源碼分析 getApplication和getApplicationContext的區(qū)別

本文打算從原理出發(fā)秩伞,通過(guò)分析源碼逞带,找到Activity的成員變量Application的出處,以此分析倆個(gè)方法的區(qū)別纱新。

背景知識(shí)

Context關(guān)系圖.png

首先上一張老生常談的圖展氓。

由圖的繼承關(guān)系可知,Activity脸爱、Application遇汞、Service都繼承自ContextWrapper;
ContextWrapper是個(gè)代理類簿废,它的部分源碼截圖如下:

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }

    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

    public Context getBaseContext() {
        return mBase;
    }

    @Override
    public AssetManager getAssets() {
        return mBase.getAssets();
    }

    @Override
    public Resources getResources() {
        return mBase.getResources();
    }
    
    ...
}

從源碼可以看出空入,ContextWrapper的方法都是通過(guò)代理"另一個(gè)"Context實(shí)現(xiàn)的。這里暫時(shí)給出結(jié)論:這個(gè)代理類就是ContextImpl族檬,它是Context的真正實(shí)現(xiàn)類歪赢。稍后我們會(huì)用源碼驗(yàn)證結(jié)論。

也就是說(shuō)导梆,Activity內(nèi)部有成員變量ContextImpl的實(shí)例轨淌,以此實(shí)現(xiàn)我們常用的Context的方法迂烁。

源碼分析

了解了上述背景知識(shí)之后,下面來(lái)正式開(kāi)始分析Activity的方法:getApplication()getApplicationContext()的區(qū)別递鹉。

getApplication

我們先來(lái)看getApplication()的源碼:

public final Application getApplication() {
    return mApplication;
}

Activity內(nèi)部有成員變量mApplication盟步,getApplication()直接返回的就是它,那么它的來(lái)源是哪呢躏结?

這里為了使流程更加清晰却盘,我將從源碼源頭分析,正推出結(jié)果媳拴,實(shí)際學(xué)習(xí)則是通過(guò)結(jié)果逆向反推出源頭的黄橘。

ActivityThread是Activity的啟動(dòng)類,通過(guò)調(diào)用ActivityThread.performLaunchActivity()屈溉,即可生成要啟動(dòng)的Activity實(shí)例塞关,本文不會(huì)專程去分析應(yīng)用程序或Activity的啟動(dòng)原理,只需要知道它是Activity實(shí)例的創(chuàng)建方法就可以了子巾。下面我們來(lái)看下這個(gè)方法的部分源碼:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

        ActivityInfo aInfo = r.activityInfo;
        ...

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ...
        } catch (Exception e) {
            ...
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation); #1

            ...

            if (activity != null) {
                ...
                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);#2
                        
                        ...

注釋1處帆赢,調(diào)用r.packageInfo.makeApplication生成了Application實(shí)例;注釋2處线梗,調(diào)用activity.attch椰于,將Application傳給了Activity。來(lái)看下``

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context); 

        ...
        mApplication = application; #1

注釋1處仪搔,直接將application賦值給了mApplication瘾婿,看來(lái)這就是我們要找的源頭。

回到上一步烤咧,r.packageInfo.makeApplication生成了Application實(shí)例偏陪,這里,r.packageInfo實(shí)際是LoadedApk對(duì)象髓削,故名思義竹挡,它通過(guò)ClassLoader去加載APK文件,以此來(lái)獲取我們需要的類立膛。不明白ClassLoader可以稍微去了解下,不了解也沒(méi)關(guān)系梯码,只需要知道通過(guò)ClassLoader可以將類文件轉(zhuǎn)化為類就可以了宝泵。

下面來(lái)看LoadedApk.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";
        }#1

        try {
            java.lang.ClassLoader cl = getClassLoader(); #2
            ...
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); #3
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);#4
            ...
        } catch (Exception e) {
            ...
        }
        ...
        mApplication = app;

        ...
        return app;
    }

這里為了理清流程,省略了部分無(wú)關(guān)主題的代碼轩娶。

注釋1處儿奶,獲取要?jiǎng)?chuàng)建的Application的ClassName,如果沒(méi)有自定義的Application鳄抒,則使用系統(tǒng)原生"android.app.Application"闯捎;
注釋2處椰弊,獲取ClassLoader,它可以通過(guò)ClassName獲取需要的Class瓤鼻。
注釋3處秉版,獲取ContextImpl,上文說(shuō)過(guò)Application繼承自ContextWrapper茬祷,它僅僅是Context的代理清焕,真正的實(shí)現(xiàn)類是ContextImpl。這一結(jié)論很快將得到證實(shí)祭犯。
在得到必要的三個(gè)參數(shù)之后秸妥,就可以創(chuàng)建Application了,注釋4處執(zhí)行了創(chuàng)建Application的方法:

    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        return newApplication(cl.loadClass(className), context);
    }

通過(guò)ClassLoader和ClassName沃粗,獲取到Class<Application>實(shí)例粥惧,然后執(zhí)行下一步:

    static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = (Application)clazz.newInstance(); #1
        app.attach(context); #2
        return app;
    }

注釋1處,通過(guò)class反射創(chuàng)建了Application實(shí)例最盅。這就是getApplication()的最終源頭影晓。

但是還沒(méi)有完,注釋2處調(diào)用了app.attach(context);檩禾,聯(lián)系上下文可以知道這個(gè)context就是之前創(chuàng)建的ContextImpl挂签,而attach最終調(diào)用的是ContextWrapper.attachBaseContext,還記得文章開(kāi)頭的ContextWrapper源碼嗎盼产?找一下這個(gè)方法饵婆,你就會(huì)發(fā)現(xiàn),Application作為ContextWrapper戏售,其真實(shí)調(diào)用的Context就是這里傳過(guò)來(lái)的ContextImpl侨核!

講到這里,我們已經(jīng)找到了getApplication()的源頭灌灾。同時(shí)也會(huì)發(fā)現(xiàn):ContextImpl從始至終都沒(méi)有與getApplication()有任何關(guān)聯(lián)搓译,那為什么還要大費(fèi)周章的講它呢?別急锋喜,還有一個(gè)方法getApplicationContext()沒(méi)說(shuō)呢些己。

getApplicationContext

先來(lái)看一下Activity.getApplicationContext()源碼,該方法是Activity的父類:ContextWrapper實(shí)現(xiàn)的嘿般。

    @Override
    public Context getApplicationContext() {
        return mBase.getApplicationContext();
    }

通過(guò)上文可知段标,mBase即是ContextImpl(但是注意不是上文Application的ContextImpl,下文會(huì)有分析)炉奴,來(lái)看下ContextImpl.getApplicationContext()的源碼:

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

mPackageInfo就是LoadedApk實(shí)例逼庞,ContextImpl.LoadedApk.getApplication()直接返回了LoadedApk的成員變量mApplication。從這里還無(wú)從得知mApplication的來(lái)源瞻赶,所以我們可以先分析ContextImpl實(shí)例的來(lái)源赛糟。

回到上文中Activity實(shí)例的創(chuàng)建方法performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

        ActivityInfo aInfo = r.activityInfo;
        ...

        ContextImpl appContext = createBaseContextForActivity(r); #1
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ...
        } catch (Exception e) {
            ...
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation); 

            ...

            if (activity != null) {
                ...
                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);#2
                        
                        ...

注釋1處派任,創(chuàng)建了ContextImpl實(shí)例。
注釋2處璧南,調(diào)用了activity.attach掌逛,還記得它的源碼嗎?

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);  #1

        ...
        mApplication = application; 

注釋1處穆咐,ContextImpl被賦值給了Activity颤诀,看來(lái)這就是ContextImpl的最初源頭。

回到上一步注釋1處对湃,來(lái)看下ContextImpl實(shí)例的創(chuàng)建過(guò)程createBaseContextForActivity(r)

    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        ...

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

        ...
        return appContext;
    }

第二個(gè)參數(shù)r.packageInfo就是LoadedApk對(duì)象崖叫,它在這里賦值給了ContextImpl。聯(lián)系上下文拍柒,也就是說(shuō)心傀,getApplicationContext()的application,就來(lái)自r.packageInfo的成員變量mApplication拆讯。

那么脂男,r.packageInfo的成員變量mApplication,又是什么時(shí)候賦值的呢种呐?

還記得上文Application是怎么創(chuàng)建的嗎宰翅?

r.packageInfo.makeApplication(false, mInstrumentation);

再來(lái)回顧一遍LoadedApk.makeApplication源碼,這次只需要關(guān)注注釋1處:

    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 = ContextImpl.createAppContext(mActivityThread, this); 
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            ...
        } catch (Exception e) {
            ...
        }
        ...
        mApplication = app; #1

        ...
        return app;
    }

在上文中創(chuàng)建的Application爽室,在賦值給Activity的成員變量mApplication的同時(shí)汁讼,還賦值給了LoadedApk的mApplication,之后LoadedApk又作為了ContextImpl的成員變量之一傳給了Activity阔墩。

至此得知嘿架,對(duì)于Activity而言,getApplication()getApplicationContext()返回的是同一個(gè)Application啸箫,它們都是LoadedApk通過(guò)ClassLoader創(chuàng)建的耸彪,只不過(guò)賦值給了不同的成員變量而已。

流程圖

下面梳理下它們的創(chuàng)建與賦值流程忘苛。

getApplication

getApplication流程圖.png

getApplicationContext

getApplicationContext流程圖.png

由流程圖可以看出蝉娜,Application的來(lái)源均是LoadedApk.makeApplication(),故倆個(gè)方法返回的是同一個(gè)Application柑土。

寫在最后

分析倆個(gè)方法的區(qū)別蜀肘,旨在理清Android的啟動(dòng)流程,加深對(duì)Android系統(tǒng)的理解稽屏,重在過(guò)程,而非結(jié)論西乖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狐榔,一起剝皮案震驚了整個(gè)濱河市坛增,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌薄腻,老刑警劉巖收捣,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異庵楷,居然都是意外死亡罢艾,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門尽纽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咐蚯,“玉大人,你說(shuō)我怎么就攤上這事弄贿〈悍妫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵差凹,是天一觀的道長(zhǎng)期奔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)危尿,這世上最難降的妖魔是什么呐萌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮谊娇,結(jié)果婚禮上肺孤,老公的妹妹穿的比我還像新娘。我一直安慰自己邮绿,他們只是感情好渠旁,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著船逮,像睡著了一般顾腊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挖胃,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天杂靶,我揣著相機(jī)與錄音,去河邊找鬼酱鸭。 笑死吗垮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凹髓。 我是一名探鬼主播烁登,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蔚舀!你這毒婦竟也來(lái)了饵沧?” 一聲冷哼從身側(cè)響起锨络,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狼牺,沒(méi)想到半個(gè)月后羡儿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡是钥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年掠归,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悄泥。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡虏冻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出码泞,到底是詐尸還是另有隱情兄旬,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布余寥,位于F島的核電站领铐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宋舷。R本人自食惡果不足惜绪撵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祝蝠。 院中可真熱鬧音诈,春花似錦、人聲如沸绎狭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)儡嘶。三九已至喇聊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蹦狂,已是汗流浹背誓篱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凯楔,地道東北人窜骄。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像摆屯,于是被迫代替她去往敵國(guó)和親邻遏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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