譯者:
本準(zhǔn)備寫一篇Context相關(guān)的文, 看到此文, 覺(jué)得很好, 先搞個(gè)"拿來(lái)主義"譯過(guò)來(lái), 作為探索Context系列的第一篇吧.
譯文:
Context可能是我們開(kāi)發(fā)App中用的最多是元素了, 也可能是最容易被誤用的...
Context對(duì)象如此常見(jiàn), 經(jīng)常各種傳遞, 用來(lái)很方便的創(chuàng)建一些情景, 諸如加載資源文件, 啟動(dòng)一個(gè)新的Activity, 獲取一個(gè)系統(tǒng)服務(wù), 獲取內(nèi)部存儲(chǔ)文件路徑, 創(chuàng)建Views等等等等(太多了...). 我寫此文的目的是想提供給你一些觀察Context如何工作的視角, 以便你可以在你的App開(kāi)發(fā)中更有效準(zhǔn)確的使用Context.
1, Context類型
并不是所有的Context都是等同的. 根據(jù)你所在的App的組件(譯者注: 組件包括Application, Activity, Service, Receiver, Provider)不同, Context略有不同:
Application Context
Application Context在你的應(yīng)用進(jìn)程里是一個(gè)單例的存在. 可以在Activity或Service中通過(guò)getApplication()來(lái)訪問(wèn), 也可以在任意繼承Context的對(duì)象中通過(guò)getApplicationContext()來(lái)訪問(wèn). 不管以何種形式訪問(wèn), 在同一應(yīng)用進(jìn)程中你獲得的Application Context實(shí)例都是同一個(gè).
Activity/Service Context
Activity/Service繼承自ContextWrapper, ContextWrapper作為Context(一個(gè)Base Context)的代理, 實(shí)現(xiàn)了和Context一樣的接口. 沒(méi)當(dāng)framework創(chuàng)建一個(gè)Activity/Service實(shí)例時(shí), 也會(huì)創(chuàng)建一個(gè)ContextImpl的實(shí)例來(lái)真正處理(Context接口所描述的)繁重的工作. 每個(gè)Activity/Service實(shí)例, 都有一個(gè)對(duì)應(yīng)的Base Context的實(shí)例.
Broadcast Receiver中的Context
實(shí)際上Broadcast Receiver并不是一個(gè)Context, 但是framework會(huì)每次廣播事件到來(lái)時(shí)傳遞一個(gè)Context給onReceive()方法. 這個(gè)Context是一個(gè)ReceiverRestrictedContext實(shí)例, 它禁用了Context的registerReceiver()和bindService() (譯者注: 這也是為什么我們說(shuō)不能在onReceive方法里面綁定一個(gè)Service的起源). 每次有廣播事件收到時(shí), 傳過(guò)來(lái)的Context都是一個(gè)新的Context實(shí)例.
ContentProvider中的Context
本身也不是一個(gè)Context, 但是可以通過(guò)getContext()方法來(lái)獲取一個(gè)Context對(duì)象. 如果ContentProvider和調(diào)用者是同一個(gè)應(yīng)用進(jìn)程, getContext()會(huì)返回一個(gè)Application級(jí)別的單例的Context實(shí)例; 然而, 如果二者處于不同的進(jìn)程, getContext()會(huì)返回一個(gè)新的代表Provider所運(yùn)行的包的Context實(shí)例.
(譯者注: 下面是作者的關(guān)于Context的使用經(jīng)驗(yàn))
2, 保存引用
第一個(gè)我們需要解決的問(wèn)題是: 在一個(gè)對(duì)象或類中保存一個(gè)Context, 但是這個(gè)對(duì)象或類的生命周期超過(guò)了你保存的Context實(shí)例的生命周期. 例如, 創(chuàng)建一個(gè)自定義的單例, 它需要一個(gè)context來(lái)加載資源或者訪問(wèn)ContentProvider, 于是保存一個(gè)指向當(dāng)前Activiy/Service的引用到該單例中.
糟糕的單例
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;
}
}
問(wèn)題是我們不知道這個(gè)context會(huì)來(lái)自哪兒, 并且保存一個(gè)最終指向Activity或Service的引用是不安全的. 因?yàn)閱卫陬惖膬?nèi)部維持一個(gè)單一的靜態(tài)引用, 意味著這個(gè)單例, 以及該單例所引用的其他所有對(duì)象都永遠(yuǎn)不會(huì)被GC回收. 如果這個(gè)context是一個(gè)Activity, 我們將會(huì)持有這個(gè)Activity以及它的所有Views和其他可能的關(guān)聯(lián)的大對(duì)象, 從而造成內(nèi)存泄露.
為了避免這種問(wèn)題, 我們使用Application類型的Context來(lái)創(chuàng)建單例:
改善后的單例
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;
}
}
現(xiàn)在, 我們將無(wú)需關(guān)注context的來(lái)源, 因?yàn)槲覀儽4娴腸ontext引用是安全的. Application Context本身就是單例, 所以我們?cè)趧?chuàng)建另一個(gè)靜態(tài)引用時(shí)不會(huì)泄露任何東西.
另一個(gè)很好的例子是, 在后臺(tái)線程或是等待中的Hanlder中也可以使用Application類型的Context.
但是為什么我們不能總是使用Application Context呢? 正如前面例子中說(shuō)的, 引用Application Context永遠(yuǎn)不用擔(dān)心內(nèi)存泄漏的問(wèn)題. 問(wèn)題的答案是, 就像我一開(kāi)始介紹的那樣, 是因?yàn)椴煌M件中的Context并不是完全一樣的.
3, Context的能力
Context能做什么決定于它來(lái)自哪兒. 下表描述了常見(jiàn)的Context的來(lái)源以及其應(yīng)用范圍:
注解:
1, Application的Context可以啟動(dòng)一個(gè)Activity, 但是會(huì)在新Task中創(chuàng)建(譯者注, 待驗(yàn)證). 這可能可以滿足一些特定需求, 但是這也會(huì)創(chuàng)建不標(biāo)準(zhǔn)的返回棧(Back Stack), 所以不推薦, 也不認(rèn)為是好的實(shí)踐.
2, 這個(gè)也是合法的, 但是Inflate出來(lái)的View是根據(jù)你當(dāng)前系統(tǒng)的默認(rèn)主題(Theme)的, 而非你的Application所使用的主題.
3, Android 4.2及以上, 如果Receiver是null(用來(lái)獲取一個(gè)Sticky Broadcast的當(dāng)前值的), 則是允許的.
4, 用戶界面
從前面的表格可以看到, 很多(UI相關(guān)的)情況下 Application Context并不適合來(lái)處理. 實(shí)際上, 只用Activity Context能夠處理所有與UI相關(guān)的任務(wù). 其他的任務(wù)所有類型的Context都差不多.
幸運(yùn)的是, 有三種事是Activity之外不能處理的, 這可能是Android framework故意這么設(shè)計(jì)的. 如果你嘗試使用Application Context去show一個(gè)對(duì)話框; 或是啟動(dòng)一個(gè)Activity, 系統(tǒng)會(huì)拋出異常, 導(dǎo)致崩潰---來(lái)提示你出問(wèn)題了...
另外一個(gè)并不明顯的是Inflate布局. 如果你讀過(guò)我另一篇關(guān)于Layout Inflation的文(譯者注, 這篇文也推薦一讀, 有空了翻譯下). 你就已經(jīng)知道它可能是一個(gè)非常神秘的過(guò)程, 隱藏著一些不可知的行為。使用正確的Context關(guān)系到其中的行為表現(xiàn). 當(dāng)你使用Application Context來(lái)inflate一個(gè)布局的時(shí)候并不會(huì)報(bào)錯(cuò), 會(huì)返回一個(gè)系統(tǒng)默認(rèn)的主題的view給你, 而沒(méi)有考慮你的Applicaiton本身的Theme和Style. 這是因?yàn)锳citivity是唯一的綁定了在manifast文件中定義主題Theme的Context. 其他的Context實(shí)例將會(huì)使用系統(tǒng)默認(rèn)的主題來(lái)inflate你的view. 這可能會(huì)導(dǎo)致顯示的View并不是你所希望的那樣的.
5, 規(guī)則的交叉點(diǎn)
顯然, 可能有些讀者已經(jīng)看出兩個(gè)規(guī)則互相矛盾之處. 在一些Application的設(shè)計(jì)中, 我們可能既需要長(zhǎng)期的保存一個(gè)引用,而且為了完成與UI相關(guān)的工作又必須保存一個(gè)Activity的Context. 如果出現(xiàn)這種情況, 我強(qiáng)烈建議你重新考慮你的設(shè)計(jì), 它將是一個(gè)很好的"反框架"案例.
6, 使用經(jīng)驗(yàn)
絕大多數(shù)情況下, 使用在你的所在的組件內(nèi)部能夠直接獲取的Context. 只要這個(gè)Context引用沒(méi)有超過(guò)這個(gè)組件的生命周期, 你就可以安全的保存這個(gè)引用. 一旦你要保存一個(gè)Context的引用到你的對(duì)象中, 該對(duì)象超過(guò)了你的Activity或者Service的生命周期范圍, 即使是暫時(shí)的, 你就需要轉(zhuǎn)換你的引用為Application Context.