Android Context詳解

一.簡(jiǎn)介

???????Android應(yīng)用模型是基于組件的應(yīng)用設(shè)計(jì)模式,組件的運(yùn)行要有一個(gè)完整的Android工程環(huán)境处硬,在這個(gè)環(huán)境下塌西,Activity掰邢、Service等系統(tǒng)組件才能夠正常工作,而這些組件并不能采用普通的Java對(duì)象創(chuàng)建方式丙唧,new一下就能創(chuàng)建實(shí)例了愈魏,而是要有它們各自的上下文環(huán)境,也就是我們這里討論的Context想际∨嗦可以這樣講,Context是維持Android程序中各組件能夠正常工作的一個(gè)核心功能類胡本。
???????源碼中的注釋是這么來解釋Context:Context提供了關(guān)于應(yīng)用環(huán)境全局信息的接口北苟。它是一個(gè)抽象類,它的執(zhí)行被Android系統(tǒng)所提供打瘪。它允許獲取以應(yīng)用為特征的資源和類型友鼻,是一個(gè)統(tǒng)領(lǐng)一些資源(應(yīng)用程序環(huán)境變量等)的上下文。
???????Context描述一個(gè)應(yīng)用程序環(huán)境的信息(即上下文)闺骚;Android提供了該抽象類的具體實(shí)現(xiàn)類彩扔;通過它我們可以獲取應(yīng)用程序的資源和類(包括應(yīng)用級(jí)別操作,如啟動(dòng)Activity僻爽,發(fā)廣播虫碉,接受Intent等)。

主要作用

??????a.四大組件的交互胸梆,包括啟動(dòng) Activity敦捧、Broadcast、Service碰镜,獲取 ContentResolver 等兢卵。
??????b.獲取系統(tǒng)/應(yīng)用資源,包括 AssetManager绪颖、PackageManager秽荤、Resources、System Service 以及 color柠横、string窃款、drawable 等。
??????c.文件牍氛,包括獲取緩存文件夾晨继、刪除文件、SharedPreference 相關(guān)等搬俊。
??????d.數(shù)據(jù)庫(kù)(SQLite)相關(guān)紊扬,包括打開數(shù)據(jù)庫(kù)曲饱、刪除數(shù)據(jù)庫(kù)、獲取數(shù)據(jù)庫(kù)路徑等珠月。

二.繼承關(guān)系

??????既然上面Context是一個(gè)抽象類扩淀,那么肯定有對(duì)應(yīng)的實(shí)現(xiàn)類,通過查看源碼啤挎,可以看到對(duì)應(yīng)的關(guān)系圖:


context繼承關(guān)系.png

a.ContextImpl類

??????ContextImpl繼承Context抽象類驻谆,實(shí)現(xiàn)了Context類中的抽象方法,是Context的具體實(shí)現(xiàn)類庆聘;它為Activity和其他應(yīng)用程序組件提供基本上下文對(duì)象胜臊,應(yīng)用中使用 Context的時(shí)候的方法就是它實(shí)現(xiàn)的。

b.ContextWrapper

??????ContextWrapper繼承Context抽象類伙判,作為Context類的包裝類象对,其內(nèi)部維護(hù)了一個(gè)Context類型的成員變量mBase,mBase最終會(huì)指向一個(gè)ContextImpl對(duì)象宴抚,ContextWrapper的方法其內(nèi)部依賴mBase勒魔,ContextWrapper是Context類的修飾類(裝飾器模式),真正的實(shí)現(xiàn)類是 ContextImpl菇曲,ContextWrapper 里面的方法調(diào)用也是調(diào)用 ContextImpl 里面的方法冠绢。

c.ContextThemeWrapper

??????ContextThemeWrapper繼承ContextWrapper,因此也擁有一個(gè)Context類型的成員變量mBase常潮,mBase最終會(huì)指向一個(gè)ContextImpl對(duì)象弟胀,它的一個(gè)直接子類就是 Activity,所以Activity也就擁有了Context提供的所有功能喊式。

d.Context類型

??????通過 Context 的繼承關(guān)系圖可以看到孵户,Activity、Service岔留、Application都是Context的子類夏哭,可以認(rèn)為Context一共有三種類型,分別是 Application贸诚、Activity 和Service方庭,他們分別承擔(dān)不同的作用,但是都屬于 Context酱固,而他們具有 Context 的功能則是由ContextImpl 類實(shí)現(xiàn)的。
??????1.Application:繼承ContextWrapper头朱,因此也擁有一個(gè)Context類型的成員變量mBase运悲,mBase最終會(huì)指向一個(gè)ContextImpl對(duì)象,ContextImpl是Context的具體實(shí)現(xiàn)類项钮,所以Application也就擁有了Context提供的所有功能班眯。
??????2.Service:繼承ContextWrapper希停,因此也擁有一個(gè)Context類型的成員變量mBase,mBase最終會(huì)指向一個(gè)ContextImpl對(duì)象署隘,ContextImpl是Context的具體實(shí)現(xiàn)類宠能,所以Service也就擁有了Context提供的所有功能。
??????3.Activity:繼承ContextThemeWrapper磁餐,ContextThemeWrapper繼承ContextWrapper违崇,因此也擁有一個(gè)Context類型的成員變量mBase,mBase最終會(huì)指向一個(gè)ContextImpl對(duì)象诊霹,ContextImpl是Context的具體實(shí)現(xiàn)類羞延,所以Activity也就擁有了Context提供的所有功能。

三.作用域

context作用域.png

??????只有 Activity 顯示界面脾还,正因?yàn)槿绱税槁幔珹ctivity 繼承的是 ContextThemeWrapper 提供一些關(guān)于主題,界面顯示的能力鄙漏,間接繼承了 ContextWrapper 嗤谚;凡是跟 UI 有關(guān)的,都應(yīng)該用 Activity 作為 Context 來處理怔蚌,否則要么會(huì)報(bào)錯(cuò)呵恢,要么 UI 會(huì)使用系統(tǒng)默認(rèn)的主題。

四.對(duì)應(yīng)Display的Context

??????該場(chǎng)景適用于多屏擴(kuò)展媚创,比如:在不同的Display上彈出Toast及addView()渗钉,Toast的顯示也是通過WindowManager.addView()來實(shí)現(xiàn)的,那么是如何區(qū)分在哪個(gè)Display上顯示呢钞钙?不兜彎子鳄橘,直接上代碼:

DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
Context context = context.createDisplayContext(displayManager.getDisplay(Display.SECOND_DISPLAY));
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
w.addView(view, params);

??????通過以上代碼就可以實(shí)現(xiàn)在對(duì)應(yīng)Display上顯示view,可以看到關(guān)鍵方法是createDisplayContext(Display display)芒炼,一起看一下該方法在 ContextImpl.java中的實(shí)現(xiàn):

@Override
public Context createDisplayContext(Display display) {

    ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
            mActivityToken, mUser, mFlags, mClassLoader);

    final int displayId = display.getDisplayId();
    context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
                null, getDisplayAdjustments(displayId).getCompatibilityInfo()));
    context.mDisplay = display;
    return context;
}

??????ContextImpl對(duì)象有變量mDisplay瘫怜,記錄了對(duì)應(yīng)的Display,之前分析過系統(tǒng)服務(wù)調(diào)用分析本刽,通過Context.getSystemService()來獲取系統(tǒng)服務(wù)鲸湃,本文以WindowManager來舉例:

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

??????可以看到,在調(diào)用SystemServiceRegistry.getSystemService(this, name)時(shí)子寓,會(huì)將this傳入暗挑,再接著往下看:

public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
    @Override
     public WindowManager createService(ContextImpl ctx) {
           return new WindowManagerImpl(ctx);
     }});

??????可以看到,在createService時(shí)會(huì)將ContextImpl作為參數(shù)傳入斜友,那么在獲取WindowManager時(shí)炸裆,返回的是其實(shí)現(xiàn)類WindowManagerImpl對(duì)象,接著往下看:

public WindowManagerImpl(Context context) {
    this(context, null);
}

private WindowManagerImpl(Context context, Window parentWindow) {
    mContext = context;
    mParentWindow = parentWindow;
}

??????在創(chuàng)建WindowManagerImpl對(duì)象時(shí)會(huì)保存mContext對(duì)象鲜屏,那么當(dāng)執(zhí)行WindowManager.addView()時(shí)烹看,看一下邏輯處理:

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

??????可以看到国拇,在執(zhí)行addView()時(shí),最終會(huì)調(diào)用到WindowManagerGlobal的addView()方法惯殊,此時(shí)會(huì)通過mContext.getDisplay()獲取到創(chuàng)建Context時(shí)傳入的Display酱吝,從而將view添加到對(duì)應(yīng)的Display上。
??????還有一種場(chǎng)景土思,比如顯式配置應(yīng)用在指定Display上進(jìn)行啟動(dòng)务热,那么該如何指定Activity的context對(duì)應(yīng)指定的Display呢?
??????我們知道浪漠,在啟動(dòng)一個(gè)Activity時(shí)陕习,通過AMS會(huì)回調(diào)到應(yīng)用進(jìn)程的ActivityThread內(nèi)部的performLaunchActivty()方法,該方法內(nèi)部會(huì)創(chuàng)建ContextImpl實(shí)例址愿,通過attach()賦值給Actiivty內(nèi)部该镣,一起看一下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    .........................
    ContextImpl appContext = createBaseContextForActivity(r);
    ...........................
    activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
    ...................
}

??????通過createBaseContextForActivity()來創(chuàng)建ContextImpl實(shí)例appContext,看一下創(chuàng)建過程:

private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
    final int displayId;
    try {
        displayId = ActivityManager.getService().getActivityDisplayId(r.token);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }

    ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

    final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
    // For debugging purposes, if the activity's package name contains the value of
    // the "debug.use-second-display" system property as a substring, then show
    // its content on a secondary display if there is one.
    String pkgName = SystemProperties.get("debug.second-display.pkg");
    if (pkgName != null && !pkgName.isEmpty()
                && r.packageInfo.mPackageName.contains(pkgName)) {
        for (int id : dm.getDisplayIds()) {
            if (id != Display.DEFAULT_DISPLAY) {
                Display display =
                            dm.getCompatibleDisplay(id, appContext.getResources());
                appContext = (ContextImpl) appContext.createDisplayContext(display);
                break;
            }
        }
    }
    return appContext;
}

??????可以看到响谓,在該方法內(nèi)部會(huì)先獲取Activity在啟動(dòng)前設(shè)置的啟動(dòng)Display對(duì)應(yīng)的displayId损合,然后通過createActivityContext()將displayId作為參數(shù)傳入,此時(shí)ContextImpl會(huì)對(duì)應(yīng)了Display信息娘纷;另外如果是調(diào)試pkgName(啟動(dòng)到SecondDisplay)嫁审,那么會(huì)創(chuàng)建SecondDisplay對(duì)應(yīng)的ContextImpl實(shí)例并返回,此時(shí)ContextImpl也會(huì)對(duì)應(yīng)Display信息赖晶;
??????擴(kuò)展問題:Service中也需要ContextImpl對(duì)應(yīng)Display律适,那么可以在handleCreateService()內(nèi)部進(jìn)行跟上述相同的修改來使Service的ContextImpl也對(duì)應(yīng)Display信息。

private void handleCreateService(CreateServiceData data) {
    .....................
    ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
    //修改為對(duì)應(yīng)Display的ContextImpl
    final DisplayManagerGlobal dmg = DisplayManagerGlobal.getInstance();
    if (packageInfo.mPackageName != null && dmg.isShowSecondDisplay(packageInfo.mPackageName)) {
       for (int id : dmg.getDisplayIds()) {
             if (id != Display.DEFAULT_DISPLAY) {
                Display display = dmg.getCompatibleDisplay(id, context.getResources());
                context = (ContextImpl) context.createDisplayContext(display);
                break;
             }
        }
    }
    ............................
    service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
    .................
}

五.正確使用Context

??????一般Context造成的內(nèi)存泄漏遏插,幾乎都是當(dāng)Context銷毀的時(shí)候捂贿,卻因?yàn)楸灰脤?dǎo)致銷毀失敗,而Application的Context對(duì)象可以理解為隨著進(jìn)程存在的胳嘲,所以我們總結(jié)出使用Context的正確姿勢(shì):
??????1:當(dāng)Application的Context能搞定的情況下厂僧,并且生命周期長(zhǎng)的對(duì)象,優(yōu)先使用Application的Context了牛。
??????2:不要讓生命周期長(zhǎng)于Activity的對(duì)象持有Activity的引用颜屠。
??????3:盡量不要在Activity中使用非靜態(tài)內(nèi)部類,因?yàn)榉庆o態(tài)內(nèi)部類會(huì)隱式持有外部類實(shí)例的引用鹰祸,如果使用靜態(tài)內(nèi)部類甫窟,將外部實(shí)例引用作為弱引用持有。

六.其他

a.在自定義MyApplication的構(gòu)造方法中使用Context

??????在自定義MyApplication的構(gòu)造方法中調(diào)用Context的getPackageName()[實(shí)際是在調(diào)用mBase.getPackageName()]時(shí)福荸,attachBaseContext(Context base) 還未被系統(tǒng)調(diào)用蕴坪,因此mBase為Null,出現(xiàn)空指針敬锐。
??????Application方法的執(zhí)行順序:構(gòu)造方法>attachBaseContext()方法>onCreate()方法背传。attachBaseContext(Context base) 是被系統(tǒng)調(diào)用的,為mBase賦值為ContextImpl類型的context台夺。

b.一個(gè)APP應(yīng)用Context數(shù)量

??????Context 一共有 Application 径玖、Activity 和 Service 三種類型,因此一個(gè)應(yīng)用程序中 Context 數(shù)量的計(jì)算公式就可以這樣寫:
??????Context 數(shù)量 = Activity 數(shù)量 + Service 數(shù)量 + 1
??????上面的1代表著 Application 的數(shù)量颤介,因?yàn)橐粋€(gè)應(yīng)用程序中可以有多個(gè)Activity和多個(gè)Service梳星,但是只能有一個(gè)Application。

c.ContextImpl實(shí)例什么時(shí)候生成

??????ContextImpl實(shí)例生成對(duì)應(yīng)著mBase的賦值過程:
??????在啟動(dòng)Activity時(shí)滚朵,在ActivityThread內(nèi)部通過handleLaunchActivity()方法一系列調(diào)用冤灾,在通過Instrucmentation創(chuàng)建完Activity后,會(huì)先調(diào)用Activity的attach()方法辕近,會(huì)傳入已創(chuàng)建好的ContextImpl對(duì)象韵吨,在Attach()方法內(nèi)部會(huì)先調(diào)用attachBaseContext(context)方法,會(huì)將ContextImpl通過super.attachBaseContext(context)一步一步最后賦值給ContextWrapper的mBase移宅,接下來再調(diào)用activity的onCreate()归粉。

d.ContentProvider里的Context初始化

??????ContentProvider本身不是Context ,但是它有一個(gè)成員變量 mContext 漏峰,是通過構(gòu)造函數(shù)傳入的糠悼。mContext初始化對(duì)應(yīng)著ContentProvider創(chuàng)建時(shí)機(jī)。
??????應(yīng)用創(chuàng)建Application是通過調(diào)用 ActivityThread.handleBindApplication方法浅乔,這個(gè)方法的相關(guān)流程有:
??????創(chuàng)建 Application
??????初始化 Application的Context
??????調(diào)用installContentProviders()并傳入剛創(chuàng)建好的context來創(chuàng)建ContentProvider
??????調(diào)用Application.onCreate()
??????ContentProvider的Context是在Applicaiton創(chuàng)建之后倔喂,但是 onCreate方法調(diào)用之前初始化的。

???????以上就是Context的詳解及使用中常見的問題靖苇!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末席噩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子顾复,更是在濱河造成了極大的恐慌班挖,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芯砸,死亡現(xiàn)場(chǎng)離奇詭異萧芙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)假丧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門双揪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人包帚,你說我怎么就攤上這事渔期。” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵疯趟,是天一觀的道長(zhǎng)拘哨。 經(jīng)常有香客問我,道長(zhǎng)信峻,這世上最難降的妖魔是什么倦青? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮盹舞,結(jié)果婚禮上产镐,老公的妹妹穿的比我還像新娘。我一直安慰自己踢步,他們只是感情好癣亚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布掸哑。 她就那樣靜靜地躺著泣侮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪躬贡。 梳的紋絲不亂的頭發(fā)上蓬豁,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天绰咽,我揣著相機(jī)與錄音,去河邊找鬼地粪。 笑死取募,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蟆技。 我是一名探鬼主播玩敏,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼质礼!你這毒婦竟也來了旺聚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤眶蕉,失蹤者是張志新(化名)和其女友劉穎砰粹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體造挽,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碱璃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饭入。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嵌器。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谐丢,靈堂內(nèi)的尸體忽然破棺而出爽航,到底是詐尸還是另有隱情蚓让,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布讥珍,位于F島的核電站历极,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏串述。R本人自食惡果不足惜执解,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一寞肖、第九天 我趴在偏房一處隱蔽的房頂上張望纲酗。 院中可真熱鬧,春花似錦新蟆、人聲如沸觅赊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吮螺。三九已至,卻和暖如春帕翻,著一層夾襖步出監(jiān)牢的瞬間鸠补,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工嘀掸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留紫岩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓睬塌,卻偏偏與公主長(zhǎng)得像泉蝌,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子揩晴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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