[譯]探索Context之Context是什么

譯者:
本準(zhǔn)備寫一篇Context相關(guān)的文, 看到此文, 覺(jué)得很好, 先搞個(gè)"拿來(lái)主義"譯過(guò)來(lái), 作為探索Context系列的第一篇吧.

原文: Context, What 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)用范圍:

Context Capabilities

注解:
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.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蜂科,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抠刺,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡摘昌,警方通過(guò)查閱死者的電腦和手機(jī)速妖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)聪黎,“玉大人罕容,你說(shuō)我怎么就攤上這事「迨危” “怎么了锦秒?”我有些...
    開(kāi)封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)喉镰。 經(jīng)常有香客問(wèn)我旅择,道長(zhǎng),這世上最難降的妖魔是什么侣姆? 我笑而不...
    開(kāi)封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任生真,我火速辦了婚禮脖咐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汇歹。我一直安慰自己屁擅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布产弹。 她就那樣靜靜地躺著派歌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痰哨。 梳的紋絲不亂的頭發(fā)上胶果,一...
    開(kāi)封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音斤斧,去河邊找鬼早抠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛撬讽,可吹牛的內(nèi)容都是我干的蕊连。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼游昼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼甘苍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起烘豌,我...
    開(kāi)封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤载庭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后廊佩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體囚聚,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年标锄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了顽铸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鸯绿,死狀恐怖跋破,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瓶蝴,我是刑警寧澤毒返,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站舷手,受9級(jí)特大地震影響拧簸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜男窟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一盆赤、第九天 我趴在偏房一處隱蔽的房頂上張望贾富。 院中可真熱鬧,春花似錦牺六、人聲如沸颤枪。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)畏纲。三九已至,卻和暖如春春缕,著一層夾襖步出監(jiān)牢的瞬間盗胀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工锄贼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留票灰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓宅荤,卻偏偏與公主長(zhǎng)得像屑迂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子膘侮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容