context的理解

轉(zhuǎn)載自:http://blog.csdn.net/singwhatiwanna/article/details/21829971 (來自singwhatiwanna的博客)

前言

Context在android中的作用不言而喻,當我們訪問當前應(yīng)用的資源,啟動一個新的activity的時候都需要提供Context务冕,而這個Context到底是什么呢蠕趁,這個問題好像很好回答又好像難以說清楚晓铆。從字面意思坏快,Context的意思是“上下文”草巡,或者也可以叫做環(huán)境属铁、場景等眠寿,盡管如此,還是有點抽象焦蘑。從類的繼承來說盯拱,Context作為一個抽象的基類,它的實現(xiàn)子類有三種:Application例嘱、Activity和Service(姑且這么說狡逢,暫時不管ContextWrapper等類),那么這三種有沒有區(qū)別呢拼卵?為什么通過任意的Context訪問資源都得到的是同一套資源呢奢浑?getApplication和getApplicationContext有什么區(qū)別呢?應(yīng)用中到底有多少個Context呢腋腮?本文將圍繞這些問題一一展開雀彼,所用源碼版本為Android4.4。

什么是Context

Context是一個抽象基類即寡,我們通過它訪問當前包的資源(getResources徊哑、getAssets)和啟動其他組件(Activity、Service聪富、Broadcast)以及得到各種服務(wù)(getSystemService)莺丑,當然,通過Context能得到的不僅僅只有上述這些內(nèi)容墩蔓。對Context的理解可以來說:Context提供了一個應(yīng)用的運行環(huán)境梢莽,在Context的大環(huán)境里,應(yīng)用才可以訪問資源钢拧,才能完成和其他組件蟹漓、服務(wù)的交互,Context定義了一套基本的功能接口源内,我們可以理解為一套規(guī)范葡粒,而Activity和Service是實現(xiàn)這套規(guī)范的子類份殿,這么說也許并不準確,因為這套規(guī)范實際是被ContextImpl類統(tǒng)一實現(xiàn)的嗽交,Activity和Service只是繼承并有選擇性地重寫了某些規(guī)范的實現(xiàn)卿嘲。

Application、Activity和Service作為Context的區(qū)別

首先夫壁,它們都間接繼承了Context拾枣,這是它們的相同點。

不同點盒让,可以從幾個方面來說:首先看它們的繼承關(guān)系

Activity的繼承關(guān)系

1.png

Service和Application的繼承關(guān)系

2.png

通過對比可以清晰地發(fā)現(xiàn)梅肤,Service和Application的類繼承關(guān)系比較像,而Activity還多了一層繼承ContextThemeWrapper邑茄,這是因為Activity有主題的概念姨蝴,而Service是沒有界面的服務(wù),Application更是一個抽象的東西肺缕,它也是通過Activity類呈現(xiàn)的左医。

下面來看一下三者在Context方面的區(qū)別

上文已經(jīng)指出,Context的真正實現(xiàn)都在ContextImpl中同木,也就是說Context的大部分方法調(diào)用都會轉(zhuǎn)到ContextImpl中浮梢,而三者的創(chuàng)建均在ActivityThread中完成,我之前寫過一篇文章Android源碼分析-Activity的啟動過程彤路,在文中我指出Activity啟動的核心過程是在ActivityThread中完成的秕硝,這里要說明的是,Application和Service的創(chuàng)建也是在ActivityThread中完成的斩萌。下面我們看下三者在創(chuàng)建時是怎么和ContextImpl相關(guān)聯(lián)的缝裤。

Activity對象中ContextImpl的創(chuàng)建

代碼為ActivityThread中的performLaunchActivity方法

if (activity != null) {
    Context appContext = createBaseContextForActivity(r, activity);
    /**
     *  createBaseContextForActivity中創(chuàng)建ContextImpl的代碼
     *  ContextImpl appContext = new ContextImpl();
     *  appContext.init(r.packageInfo, r.token, this);
     *  appContext.setOuterContext(activity);
     */
    CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
    Configuration config = new Configuration(mCompatConfiguration);
    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
            + r.activityInfo.name + " with config " + config);
    activity.attach(appContext, this, getInstrumentation(), r.token,
            r.ident, app, r.intent, r.activityInfo, title, r.parent,
            r.embeddedID, r.lastNonConfigurationInstances, config);
 
    if (customIntent != null) {
        activity.mIntent = customIntent;
    }
    ...
}

可以看出,Activity在創(chuàng)建的時候會new一個ContextImpl對象并在attach方法中關(guān)聯(lián)它颊郎,需要注意的是,創(chuàng)建Activity使用的數(shù)據(jù)結(jié)構(gòu)是ActivityClientRecord霎苗。

Application對象中ContextImpl的創(chuàng)建

代碼在ActivityThread中的handleBindApplication方法中姆吭,此方法內(nèi)部調(diào)用了makeApplication方法

public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    if (mApplication != null) {
        return mApplication;
    }
 
    Application app = null;
 
    String appClass = mApplicationInfo.className;
    if (forceDefaultAppClass || (appClass == null)) {
        appClass = "android.app.Application";
    }
 
    try {
        java.lang.ClassLoader cl = getClassLoader();
        ContextImpl appContext = new ContextImpl();
        appContext.init(this, null, mActivityThread);
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        appContext.setOuterContext(app);
    } catch (Exception e) {
        if (!mActivityThread.mInstrumentation.onException(app, e)) {
            throw new RuntimeException(
                "Unable to instantiate application " + appClass
                + ": " + e.toString(), e);
        }
    }
    ...
}

看代碼發(fā)現(xiàn)和Activity中ContextImpl的創(chuàng)建是相同的。

Service對象中ContextImpl的創(chuàng)建

通過查看代碼發(fā)現(xiàn)和Activity唁盏、Application是一致的内狸。分析到這里,那么三者的Context有什么區(qū)別呢厘擂?沒有區(qū)別嗎昆淡?盡管如此,有一些細節(jié)是確定的:Dialog的使用需要Activity刽严,在桌面上我們采用Application的Context無法彈出對話框昂灵,同時在桌面上想啟動新的activity,我們需要為intent設(shè)置FLAG_ACTIVITY_NEW_TASK標志,否則無法啟動activity眨补,這一切都說明管削,起碼Application的Context和Activity的Context還是有區(qū)別的,當然這也可能不是Context的區(qū)別撑螺,因為在桌面上含思,我們的應(yīng)用沒有界面,這意味著我們能干的事情可能受到了限制甘晤,事情的細節(jié)目前我還沒有搞的很清楚含潘。

Context對資源的訪問

很明確,不同的Context得到的都是同一份資源线婚。這是很好理解的调鬓,請看下面的分析

得到資源的方式為context.getResources,而真正的實現(xiàn)位于ContextImpl中的getResources方法酌伊,在ContextImpl中有一個成員 private Resources mResources腾窝,它就是getResources方法返回的結(jié)果,mResources的賦值代碼為:

mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);

下面看一下ResourcesManager的getTopLevelResources方法居砖,這個方法的思想是這樣的:在ResourcesManager中虹脯,所有的資源對象都被存儲在ArrayMap中,首先根據(jù)當前的請求參數(shù)去查找資源奏候,如果找到了就返回循集,否則就創(chuàng)建一個資源對象放到ArrayMap中。有一點需要說明的是為什么會有多個資源對象蔗草,原因很簡單咒彤,因為res下可能存在多個適配不同設(shè)備、不同分辨率咒精、不同系統(tǒng)版本的目錄镶柱,按照android系統(tǒng)的設(shè)計,不同設(shè)備在訪問同一個應(yīng)用的時候訪問的資源可以不同模叙,比如drawable-hdpi和drawable-xhdpi就是典型的例子歇拆。

public Resources getTopLevelResources(String resDir, int displayId,
        Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
    final float scale = compatInfo.applicationScale;
    ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
            token);
    Resources r;
    synchronized (this) {
        // Resources is app scale dependent.
        if (false) {
            Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
        }
        WeakReference<Resources> wr = mActiveResources.get(key);
        r = wr != null ? wr.get() : null;
        //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
        if (r != null && r.getAssets().isUpToDate()) {
            if (false) {
                Slog.w(TAG, "Returning cached resources " + r + " " + resDir
                        + ": appScale=" + r.getCompatibilityInfo().applicationScale);
            }
            return r;
        }
    }
 
    //if (r != null) {
    //    Slog.w(TAG, "Throwing away out-of-date resources!!!! "
    //            + r + " " + resDir);
    //}
 
    AssetManager assets = new AssetManager();
    if (assets.addAssetPath(resDir) == 0) {
        return null;
    }
 
    //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
    DisplayMetrics dm = getDisplayMetricsLocked(displayId);
    Configuration config;
    boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
    final boolean hasOverrideConfig = key.hasOverrideConfiguration();
    if (!isDefaultDisplay || hasOverrideConfig) {
        config = new Configuration(getConfiguration());
        if (!isDefaultDisplay) {
            applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
        }
        if (hasOverrideConfig) {
            config.updateFrom(key.mOverrideConfiguration);
        }
    } else {
        config = getConfiguration();
    }
    r = new Resources(assets, dm, config, compatInfo, token);
    if (false) {
        Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
                + r.getConfiguration() + " appScale="
                + r.getCompatibilityInfo().applicationScale);
    }
 
    synchronized (this) {
        WeakReference<Resources> wr = mActiveResources.get(key);
        Resources existing = wr != null ? wr.get() : null;
        if (existing != null && existing.getAssets().isUpToDate()) {
            // Someone else already created the resources while we were
            // unlocked; go ahead and use theirs.
            r.getAssets().close();
            return existing;
        }
 
        // XXX need to remove entries when weak references go away
        mActiveResources.put(key, new WeakReference<Resources>(r));
        return r;
    }
}

根據(jù)上述代碼中資源的請求機制,再加上ResourcesManager采用單例模式范咨,這樣就保證了不同的ContextImpl訪問的是同一套資源故觅,注意,這里說的同一套資源未必是同一個資源渠啊,因為資源可能位于不同的目錄输吏,但它一定是我們的應(yīng)用的資源,或許這樣來描述更準確替蛉,在設(shè)備參數(shù)和顯示參數(shù)不變的情況下贯溅,不同的ContextImpl訪問到的是同一份資源拄氯。設(shè)備參數(shù)不變是指手機的屏幕和android版本不變,顯示參數(shù)不變是指手機的分辨率和橫豎屏狀態(tài)盗迟。也就是說坤邪,盡管Application、Activity罚缕、Service都有自己的ContextImpl艇纺,并且每個ContextImpl都有自己的mResources成員,但是由于它們的mResources成員都來自于唯一的ResourcesManager實例邮弹,所以它們看似不同的mResources其實都指向的是同一塊內(nèi)存(C語言的概念)黔衡,因此,它們的mResources都是同一個對象(在設(shè)備參數(shù)和顯示參數(shù)不變的情況下)腌乡。在橫豎屏切換的情況下且應(yīng)用中為橫豎屏狀態(tài)提供了不同的資源盟劫,處在橫屏狀態(tài)下的ContextImpl和處在豎屏狀態(tài)下的ContextImpl訪問的資源不是同一個資源對象。

代碼:單例模式的ResourcesManager類

    public static ResourcesManager getInstance() {
        synchronized (ResourcesManager.class) {
            if (sResourcesManager == null) {
                sResourcesManager = new ResourcesManager();
            }
            return sResourcesManager;
        }
    }

getApplication和getApplicationContext的區(qū)別

getApplication返回結(jié)果為Application与纽,且不同的Activity和Service返回的Application均為同一個全局對象侣签,在ActivityThread內(nèi)部有一個列表專門用于維護所有應(yīng)用的application:

final ArrayList<Application> mAllApplications = new ArrayList<Application>()

為什么說getApplication返回的都是同一個Application對象呢,是因為Activity和Service的getApplication返回的Application對象是由ActivityThread創(chuàng)建它們的時候通過它們的attach方法來傳遞給它們的急迂,也就是說所有Activity和Service所持有的Application均是ActivityThread內(nèi)部的Application影所,由于一個應(yīng)用只有一個包信息,所以ActivityThread內(nèi)部只可能創(chuàng)建出一個Application僚碎,原因是當執(zhí)行packageInfo.makeApplication的時候猴娩,如果已經(jīng)創(chuàng)建過Application了,packageInfo.makeApplication方法就不會再創(chuàng)建新的Application勺阐。關(guān)于一個應(yīng)用只有一個包信息卷中,從代碼的邏輯來看的確是這樣的,在ActivityThread內(nèi)部同樣有一個列表專門用于維護所有應(yīng)用的包信息:

final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<String, WeakReference<LoadedApk>>()

getApplicationContext返回的也是Application對象渊抽,只不過返回類型為Context蟆豫,看看它的實現(xiàn)

    @Override
    public Context getApplicationContext() {
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : mMainThread.getApplication();
    }

上面代碼中mPackageInfo是包含當前應(yīng)用的包信息、比如包名腰吟、應(yīng)用的安裝目錄等无埃,原則上來說,作為第三方應(yīng)用毛雇,包信息mPackageInfo不可能為空,在這種情況下侦镇,getApplicationContext返回的對象和getApplication是同一個灵疮。但是對于系統(tǒng)應(yīng)用,包信息有可能為空壳繁,具體就不深入研究了震捣。從這種角度來說荔棉,對于第三方應(yīng)用,一個應(yīng)用只存在一個Application對象蒿赢,且通過getApplication和getApplicationContext得到的是同一個對象润樱,兩者的區(qū)別僅僅是返回類型不同。

應(yīng)用中Context的數(shù)量

到此已經(jīng)很明了了羡棵,一個應(yīng)用中Context的數(shù)量等于Activity的個數(shù) + Service的個數(shù) + 1壹若,這個1為Application

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市皂冰,隨后出現(xiàn)的幾起案子店展,更是在濱河造成了極大的恐慌,老刑警劉巖秃流,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赂蕴,死亡現(xiàn)場離奇詭異,居然都是意外死亡舶胀,警方通過查閱死者的電腦和手機概说,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嚣伐,“玉大人糖赔,你說我怎么就攤上這事∠丝兀” “怎么了挂捻?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長船万。 經(jīng)常有香客問我刻撒,道長,這世上最難降的妖魔是什么耿导? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任声怔,我火速辦了婚禮,結(jié)果婚禮上舱呻,老公的妹妹穿的比我還像新娘醋火。我一直安慰自己,他們只是感情好箱吕,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布芥驳。 她就那樣靜靜地躺著,像睡著了一般茬高。 火紅的嫁衣襯著肌膚如雪兆旬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天怎栽,我揣著相機與錄音丽猬,去河邊找鬼宿饱。 笑死,一個胖子當著我的面吹牛脚祟,可吹牛的內(nèi)容都是我干的谬以。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼由桌,長吁一口氣:“原來是場噩夢啊……” “哼为黎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沥寥,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤碍舍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后邑雅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體片橡,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡奏司,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年延都,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞒渠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丹皱。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖碧聪,靈堂內(nèi)的尸體忽然破棺而出汤善,到底是詐尸還是另有隱情绘雁,我是刑警寧澤洞难,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布舆吮,位于F島的核電站,受9級特大地震影響队贱,放射性物質(zhì)發(fā)生泄漏色冀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一柱嫌、第九天 我趴在偏房一處隱蔽的房頂上張望锋恬。 院中可真熱鬧,春花似錦编丘、人聲如沸与学。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽索守。三九已至,卻和暖如春抑片,著一層夾襖步出監(jiān)牢的瞬間蕾盯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工蓝丙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留级遭,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓渺尘,卻偏偏與公主長得像挫鸽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鸥跟,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

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