一、Context概念理解 Google解釋如下: Interface to global information about an application environment. This is an abstract class whose implementation is provided ...
一、Context概念理解
Google解釋如下:
Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.
Context是一個訪問進程環境全局信息的介面,通過它可以訪問進程特定的資源和類,也可以調用進程級別的方法。
上圖列出了Context聲明的一些主要方法:從中我們可以看出它既能獲取進程id、包信息和許可權等進程層面的信息;也能訪問如Assets、Cache、Sp、Resource和Database甚至ContentResolver等資源文件;還能啟動Activity、綁定Service、註冊Receiver和發送Broadcast等。
那麼為什麼要設計Context呢?它的意義是什麼呢?Context的字面意思是“上下文”。誰的“上下文”呢?是進程的“上下文”,也是我們開發App應用的“上下文”。Context聲明瞭一個App最基本的權利。我們創建一個空項目,即使什麼業務邏輯也不寫,我們依然可以獲取進程id,可以訪問Resource,也可以啟動一個Activity或者Service,這就是App的基本權利;但是我們獲取進程id乾什麼,Resource里有什麼資源,啟動一個Activity要展示怎樣的界面,綁定一個Service又想做哪些複雜的演算法?這些都是我們基於基本權利之上的業務邏輯!當然,同時Context也規定了App的邊界,App無法向系統要求聲明之外的過分要求,比如從應用層面,我們不能修改UserId。簡而言之,Context可以理解為系統和App之間的一份權利聲明!
二、Context架構設計
Context 是一個純抽象類,規範了App和系統之間的交互。ContextImpl真正實現Context所有函數。ContextWrapper則只是對Context做了簡單的封裝,其內部所有繼承函數的實現都是由ContextImpl實例代理完成的。正因如此,所有ContextWrapper實例被系統創建時都會通過attachBaseContext( )方法將一個ContextImpl實例賦值給其全局變數mBase。
這樣的分層設計在面向對象程式設計里非常普遍,從設計模式角度來講,這是一個標準的代理模式!這樣的設計可以讓抽象層可以更加專註於問題領域的分析和設計,而不必糾纏於具體實現,職責清晰,擴展性強。
系統為App提供了三種主要的Context組件:Application,Service,Activity。ContextThemeWrapper主要包含了與主題相關的介面,只有Activity才需要主題。拿到這三種組件中任意一個就可以實現幾乎所有Context聲明的權利,在日常開發中我們幾乎沒有關註過mContext實例具體是哪種類型。但是在某些特殊情況下,可能會因為Context類型使用不當造成RuntimeException異常,後面會做詳細解釋。那麼一個進程中到底有多少個Context呢?一般我們只考慮Application、Service和Activity這三種類型,因此:
$$Context數量=Activity數量+Service數量+1$$
Context數量是實例化的Activity和Service數量之和再加一個Application,而不是有些人誤認為的一個App只有一個“上下文”。另外一個需要註意的是,Android中的Context對象並不是像Java中那樣隨意new出來的,而是由系統在需要時創建的,具體代碼在ActivityThread類中。
三、Context實戰應用
我們已經知道Context是App要求系統兌現權利的法寶,而且一個App進程中可能有很多這樣的法寶,但是有些法寶卻並非在所有場景中總能顯靈。因為,出於Code規範或安全因素等,系統限制了某些類型Context履行某些功能。畢竟,權力是系統給的,它也有責任防止濫用而造成隱患!比如,如果想在Service里啟動一個Activity,就會造成如下異常:
throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?");
因為啟動Service是不會創建任務棧的,那麼從Service中啟動的Activity就無棧可存。如果強行加上FLAG_ACTIVITY_NEW_TASK新建一個棧,也不是標準的方式,不建議這樣設計。
下圖給出了不同類型Context具體使用範圍和限制:
- 總體上Context的操作都是允許的
- 除Activity外其他類型Context不能直接startActivity( ),需要追加FLAG_ACTIVITY_NEW_TASK
- BroadcastReceiver中不允許綁定Service,這是因為靜態註冊的Receiver,系統返回的Context類型是ReceiverRestrictedContext,查看源碼可以看到其中重寫了bindService( )方法並直接拋出ReceiverCallNotAllowedException異常;動態註冊的Receiver返回的是註冊時的Context,另行討論。
- BroadcastReceiver中允許通過registerReceiver(null, filter)方法來獲取粘性廣播,但不允許註冊常駐的Receiver。否則,同樣會收到ReceiverCallNotAllowedException異常。