#What Context? 英文原文:Context, What Context? 譯文Github地址:Context, What Context?android
##Contextgit
Context 估計是 Android 開發中最經常使用的元素了,它的獲取和使用如此廣泛,加載資源,啓動新的 Activity,獲取系統服務,獲取內部文件路徑以及建立 View 都離不開 Context。同時,Context 也是最容易誤操做的元素,以至於很容易把你帶到坑裏面去。下面就讓咱們全面對比了解一下 Context,讓你的開發更駕輕就熟。github
##Context 類型安全
不一樣類型的Context各異:根據 Android 組件的不一樣,得到到的 Context 也是不一樣的。app
###Applicationide
Application 是存在於 app 進程的一個單例 Context 對象。在 Activity 或者 Service 中,可使用 getApplication() 方法獲取這個 Application 對象。除此以外,從他繼承了 Context 的組件,均可以經過 getApplicationContext() 來獲取到這個 Application 對象。不過,不管是經過什麼方法得到,最後獲得的 Application 對象都是同一個。線程
【譯者注:這個單例 Context 對象在後文用 application context 指代】設計
###Activity/Service代理
Activity/Service 繼承自同一個基類 Context —— ContextWrapper,所以二者擁有相同的 Context API,可是具體任務仍是經過將調用委託代理給實際的內部對象來完成。每當你建立一個新的 Activity 或者 Service 的時候,同時就會建立一個新的 ContextImpl,ContextImpl 就是最終處理全部 Context API 方法的內部對象。不一樣的Activity 或者 Service 的 Context 都是不同的。code
###BroadcastReceiver
BroadcastReceiver 自己並非一個 Context,可是 Android framework 會在每個廣播事件發生的時候,給相應 BroadcastReceiver 的 onReceive() 傳遞一個 Context,這個 ReceiverRestrictedContext 有個兩個方法不可用: registerReceiver() 和 bindService()。BroadcastReceiver 每次處理 broadcast 的時候,傳遞給它的 Context 都是一個新的實例。
###ContentProvider
ContentProvider 自己也不是 Context,可是調用 getContext() 能夠獲取一個 Context。若是調用者與 ContentProvider 運行在同一個進程內,那麼這個返回的 Context 就是上文的 application context。若是調用者與 ContentProvider 不是運行在同一個進程以內,那麼這個方法會返回一個指代 provider 所在包的新 Context 實例。
##保存 Context 引用
第一個問題:當咱們將一個 Context 的引用保存到一個存活時間比 Context 自己生命週期還長的對象時,問題就來了。好比,咱們有一個單例對象,要求使用一個 Context 來執行資源加載或訪問 ContentProvider,而且傳入的是一個 Activity 或 Service 對象:
錯誤的 Singleton 實現:
public class CustomManager { private static CustomManager sInstance; public static CustomManager getInstance(Context context) { if (sInstance == null) { sInstance = new CustomManager(context); } return sInstance; } private Context mContext; private CustomManager(Context context) { mContext = context; } }
咱們知道,單例對象是一個靜態變量,受其所在類的生命週期控制。這就意味着,在這個單例對象的存活時間內,這個單例持有的對象(引用)都不會被垃圾回收。 因此這種實現的問題就是你沒法知道 Context 到底來自何處,若是這個 Context 是一個 Activity 或者 Service, 就會變得不安全:這個被持有的 Activity,以及其內部全部的 View 或者其餘的耗內存對象都沒法被回收,從而引起內存泄露。
爲了防止這種問題,咱們改成讓單例持有 application context:
改進的實現:
public class CustomManager { private static CustomManager sInstance; public static CustomManager getInstance(Context context) { if (sInstance == null) { //Always pass in the Application Context sInstance = new CustomManager(context.getApplicationContext()); } return sInstance; } private Context mContext; private CustomManager(Context context) { mContext = context; } }
如此以來,咱們就不用再關心 Context 來自哪裏,也不用關心 Context 是什麼類型,由於最終持有的都會是 application context,所以就避免了內存泄露的問題。這個處理技巧在後臺線程或者 Handler 處理中一樣有效。
既然如此,是否是意味着咱們能夠在任何狀況下都用 application context 來處理呢?這樣就永遠不用擔憂內存泄露了。答案顯然是否認的,就如上文說的同樣,不一樣狀況下的 Context 各不相同。這就像葫蘆娃同樣,雖然都是葫蘆娃,可是每一個娃的技能都不同。
##Context 特色
Context 能實現哪些功能,主要仍是取決於 Context 從何而來,下表列出了不一樣 Context 的一些不一樣點:
<table border="1" width="90%" align="center"> <thead> <tr> <th></th> <th align="center">Application</th> <th align="center">Activity</th> <th align="center">Service</th> <th align="center">ContentProvider</th> <th align="center">BroadcastReceiver</th> </tr> </thead> <tbody> <tr> <td>Show a Dialog</td> <td align="center">NO</td> <td align="center">YES</td> <td align="center">NO</td> <td align="center">NO</td> <td align="center">NO</td> </tr> <tr> <td>Start an Activity</td> <td align="center">NO<sup>1</sup></td> <td align="center">YES</td> <td align="center">NO<sup>1</sup></td> <td align="center">NO<sup>1</sup></td> <td align="center">NO<sup>1</sup></td> </tr> <tr> <td>Layout Inflation</td> <td align="center">NO<sup>2</sup></td> <td align="center">YES</td> <td align="center">NO<sup>2</sup></td> <td align="center">NO<sup>2</sup></td> <td align="center">NO<sup>2</sup></td> </tr> <tr> <td>Start a Service</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> </tr> <tr> <td>Bind to a Service</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">NO</td> </tr> <tr> <td>Send a Broadcast</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> </tr> <tr> <td>Register BroadcastReceiver</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">NO<sup>3</sup></td> </tr> <tr> <td>Load Resource Values</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> <td align="center">YES</td> </tr> </tbody> </table>
##User Interface
從上面的列表能夠看到,application context 不能勝任不少場景,並且都是與UI相關的狀況。實際上,只有 Activity 擁有處理 UI 的能力,其餘類型的 Context 在這方面都大同小異。
上面的三種行爲,除了 Activity, 其餘的 Context 也都沒法處理,從而避免誤用。試圖顯示一個使用 application context 建立的 Dialog,或者從 application context 啓動一個Activity,都會致使 app 崩潰,系統經過這種方式告訴你:你用錯了。
另外一個問題就是 inflating layout。若是你讀過 layout inflation一文, 你就會知道 inflating layout 有一些容易讓人迷惑的地方,如何正確使用 Context 就是其中一個。若是你使用 application context 來進行 inflating layout,並不會發生任何錯誤,可是當前 app 所定義的 themes 以及 styles 都被忽略了。究其緣由,正如你在 manifest 中定義的同樣, 只有 Activity 纔是惟一可以響應 themes 定義的組件。任何其餘組件所含有的 Context 都會應用 Android 系統自身的主題,因此,最終的 UI 表現可能出乎你的意料。
##規則衝突
可能有人會提出這樣一種場景:在 app 的當前設計下,因爲涉及處處理 UI 的操做,因此須要長期持有一個 Activity 的引用。若是是這樣的話,我只能說:請從新考慮你的設計。
##經驗法則
簡而言之,在組件的生命週期以內能夠直接使用自身的 Context, 一旦須要在超出組件生命週期以外的對象中使用 Context ,就應該只用 application context,哪怕只是臨時的引用,也是如此。
####Android分享 Q羣:315658668