作者簡(jiǎn)介? 創(chuàng)微信公眾號(hào)郭霖 WeChat ID: guolin_blog
本篇來自WizardDragon的投稿凉倚,分享了他對(duì)于四大組件啟動(dòng)時(shí)一些方法的調(diào)用順序的研究結(jié)果,并且深入源碼去分析遇到的問題。文章篇幅不短掺冠,希望能對(duì)大家有所幫助来颤。
WizardDragon的博客地址:
http://blog.csdn.net/long117long
背景
在做一個(gè)項(xiàng)目時(shí),我們想在應(yīng)用最早啟動(dòng)時(shí)囤躁,先做一些判斷冀痕,然后根據(jù)判斷的結(jié)果再?zèng)Q定要不要對(duì)其他應(yīng)用提供服務(wù)荔睹。
對(duì)其他應(yīng)用提供服務(wù)指的是,我們的應(yīng)用中有 ContentProvider言蛇,第三方應(yīng)用通過 call 方法調(diào)用到我們提供的 ContentProvider僻他,ContentProvider 執(zhí)行邏輯后并給調(diào)用的返回結(jié)果。當(dāng)?shù)谌綉?yīng)用調(diào)用我們的應(yīng)用時(shí)腊尚,我們的應(yīng)用存在啟動(dòng)和未啟動(dòng)的兩種情況吨拗。
剛開始,我們將判斷邏輯寫在了自定義的 Application 的 onCreate 方法中婿斥,但等到測(cè)試時(shí)發(fā)現(xiàn)了很多意想不到的情況劝篷,比如:
邏輯判斷之后的結(jié)果是不給第三方應(yīng)用提供“服務(wù)”,但有時(shí)候第三方應(yīng)用能夠使用服務(wù)民宿,而有時(shí)候第三方應(yīng)用不能使等等的問題娇妓。
于是我們跟蹤代碼,發(fā)現(xiàn)了?四大組件 以及 Application 的各個(gè)方法( attachBaseContext活鹰、onCreate哈恰、call 等)啟動(dòng)順序,跟我們之前理解的稍稍不一樣志群。
在弄清楚了 四大組件 和 Application?在應(yīng)用冷啟動(dòng)時(shí)的執(zhí)行順序后着绷,我們才把遇到的問題徹底解決。
驗(yàn)證試驗(yàn)
為了測(cè)試?四大組件 和 Application 的各種方法( attachBaseContext锌云、onCreate荠医、call 等)被系統(tǒng)調(diào)用的順序,我們創(chuàng)建一個(gè)簡(jiǎn)單的應(yīng)用宾抓,只包含這5個(gè)組件子漩,不考慮一個(gè)應(yīng)用多進(jìn)程的情況,代碼分別為:
MainApplication.java
MainActivity.java
MainService.java
MainReceiver.java
MainProvider.java
在以下幾個(gè)場(chǎng)景測(cè)試時(shí)石洗,均已冷啟動(dòng)的方式啟動(dòng)應(yīng)用幢泼。
冷啟動(dòng),指的是在系統(tǒng)沒有創(chuàng)建apk這個(gè)進(jìn)程時(shí)啟動(dòng)apk讲衫。
注意在測(cè)試的手機(jī)上缕棵,不要讓測(cè)試的應(yīng)用被禁止關(guān)聯(lián)啟動(dòng)或自啟動(dòng):
場(chǎng)景一,點(diǎn)擊桌面的圖標(biāo)啟動(dòng)應(yīng)用涉兽,日志如下:
場(chǎng)景二招驴,通過另外一個(gè)應(yīng)用以啟動(dòng)Service的形式啟動(dòng)應(yīng)用,其中啟動(dòng) MainService 的代碼如下:
日志如下:
場(chǎng)景三枷畏,應(yīng)用通過接受開機(jī)廣播啟動(dòng)的方式啟動(dòng)别厘,日志如下:
場(chǎng)景四,其他應(yīng)用調(diào)用 ContentProvider 的 call 方法啟動(dòng)拥诡,其中触趴,調(diào)用 MainProvider 的 call 代碼如下:
日志如下:
結(jié)論:
從上面四個(gè)場(chǎng)景可以看出:
1.Application 的 attachBaseContext 方法是優(yōu)先執(zhí)行的氮发;
2.ContentProvider 的 onCreate 的方法比 Application 的 onCreate 的方法先執(zhí)行;
3.Activity冗懦、Service的 onCreate 方法以及 BroadcastReceiver 的 onReceive 方法爽冕,是在 MainApplication 的 onCreate 方法之后執(zhí)行的;
4.調(diào)用流程為: Application 的 attachBaseContext ---> ContentProvider 的 onCreate ----> Application 的 onCreate ---> Activity披蕉、Service 等的 onCreate(Activity 和 Service 不分先后)颈畸;
問題
問題一:ContentProvider 的 onCreate 一定是優(yōu)先于 Application 的 onCreate 執(zhí)行的嗎?
為了驗(yàn)證這個(gè)問題没讲,MainApplication 的代碼不變眯娱,我們將 MainProvider 的 onCreate 的代碼改為:
我們?cè)僭谏厦娴谒姆N場(chǎng)景上進(jìn)行驗(yàn)證,日志如下:
問題一結(jié)論:
確實(shí)是在 ContentProvider 的 onCreate 執(zhí)行完成之后食零,才會(huì)執(zhí)行 Application 的 onCreate 的困乒。
問題二:ContentProvider中 的 call方法 是在 Application 的 onCreate 執(zhí)行完之后才執(zhí)行的嗎寂屏?
為了驗(yàn)證這個(gè)問題贰谣,我們將 MainProvider 和 MainApplication 的代碼改為:
我們還在第四個(gè)場(chǎng)景下驗(yàn)證,日志如下:
從日志中可以發(fā)現(xiàn)迁霎,Application 的 onCreate 執(zhí)行時(shí)吱抚,ContentProvider 的 call方法 也在同時(shí)執(zhí)行。
問題二結(jié)論:
Application 的 onCreate方法 和 Provider 的 call方法不是順序執(zhí)行考廉,而是會(huì)同時(shí)執(zhí)行秘豹。
問題三:有比 Application 的 attachBaseContext方法 更早執(zhí)行的方法嗎?
有昌粤,比如:Application所在類的構(gòu)造方法既绕。為了驗(yàn)證這個(gè)問題,將代碼改為:
程序啟動(dòng)后涮坐,日志為:
問題三結(jié)論:
Application 的構(gòu)造方法早于?Application 的 attachBaseContext方法 調(diào)用凄贩。
那么有沒有比 Application 的構(gòu)造方法還早被調(diào)用的方法呢?有袱讹,自己可以再想想哦疲扎。
遇到的坑
好了,我們知道 attachBaseContext 的方法在“一般情況下是最早執(zhí)行的“捷雕,那么在項(xiàng)目中為了能”盡早“的提前初始化某些模塊椒丧、功能或者參數(shù),那么我們會(huì)把代碼從 Application 的onCreate方法 提前到 attachBaseContext方法 中救巷。嗯壶熏,一切感覺起來那么美好,直到你運(yùn)行程序崩潰時(shí)...
好吧好吧浦译,那些“坑”終于還是來了棒假。
“坑”一:在 Application 的 attachBaseContext方法 中俄占,使用了 getApplicationContext方法。
當(dāng)我發(fā)現(xiàn)在 attachBaseContext方法 中使用 getApplicationContext方法 返回null時(shí)淆衷,內(nèi)心是崩潰缸榄。
所以,如果在 attachBaseContext方法 中要使用 context 的話祝拯,那么使用this吧甚带,別再使用 getApplicationContext() 方法了。下文有分析為什么佳头。
“坑”二:這個(gè)其實(shí)不算很坑鹰贵,也不會(huì)引起崩潰,但需要注意:
在 Application 的 attachBaseContext方法 中康嘉,去調(diào)用自身的?ContentProvider碉输,那么這個(gè)?ContentProvider?會(huì)被初始化兩次,也就是說這個(gè) ContentProvider 會(huì)被兩次調(diào)用到onCreate亭珍。如果你在 ContentProvider 的 onCreate 中有一些邏輯敷钾,那么一定要檢查是否會(huì)有影響。
做一下驗(yàn)證肄梨,在 Application 中調(diào)用 Provider 的 call方法阻荒,并在 MainActivity 中的 onCreate方法 中調(diào)用 Provider 的 call方法,Application 的代碼众羡,Provider 的代碼侨赡,Activity 的代碼分別如下:
啟動(dòng)應(yīng)用后,日志如下:
可以看到粱侣,MainProvider 的 onCreate 的方法被調(diào)用了兩次(因?yàn)?MainProvider 的兩次 onCreate 打印出的自身對(duì)象不一樣)羊壹,而在 MainActivity 中調(diào)用到 call方法 執(zhí)行的類,跟 MainApplication 在 attachBaseContext方法 執(zhí)行的類是同一個(gè)齐婴。
源碼分析
好了油猫,現(xiàn)象、問題和“坑”都經(jīng)歷了一遍尔店,那么 為什么 會(huì)是這樣呢眨攘?
我們通過看源碼,來跟蹤:
1.Application 的 attachBaseContext嚣州、ContentProvider 的 onCreate 以及 Application 的 onCreate 在源碼中的調(diào)用順序鲫售。
2.為什么在 Application 的 attachBaseContext 中調(diào)用 getApplicationContext 得到的是null。
先看第一個(gè)問題该肴,我們知道Java進(jìn)程的入口方法一般都是在main中情竹,而Android為了讓應(yīng)用開發(fā)者不需要關(guān)心應(yīng)用的創(chuàng)建,已經(jīng)幫我們進(jìn)行封裝匀哄,其實(shí)應(yīng)用 main方法 是在 ActivityThread.java?中的秦效。
我們查看 ActivityThread.java 的源碼雏蛮,本文以下的源碼都以6.0.1_r10基礎(chǔ)。
a. ActivityThread.java 的 main方法:
b. ActivityThread.java 的 attach方法:
c. ActivityThread.java 的 handleBindApplication(AppBindData data)方法:
d. LoaderApk.java 的 makeApplication方法
e. Instrumentation.java的相關(guān)方法
f. Application.java 的 attach方法
g.?ActivityThread.java 的 handleBindApplication方法:
h. 繼續(xù)跟蹤 installContentProviders 這個(gè)方法阱州,而這個(gè)方法是會(huì)調(diào)用到 installProvider方法 中的挑秉,還是在 ActivityThread.java 中:
i. 看 ContentProvider.java 中的 attachInfo方法(frameworks/base/core/java/android/content/ContentProvider.java)
j. 關(guān)于 注釋7 的 mInstrumentation.callApplicationOnCreate(app) 調(diào)用到的?Instrumentation.java 中的方法
看第二問題,為什么在我們自定義 Application 中的 attachBaseContext方法 中苔货,調(diào)用 getApplicationContext() 為 null 呢犀概?
1. 跟蹤 getApplicationContext() 發(fā)現(xiàn)是在 ContextWrapper.java 中實(shí)現(xiàn)的:
2. 我們看 ContextImpl 的 getApplicationContext方法:
3. mPackageInfo 是什么時(shí)候賦值的呢?我們從 ContextImpl 實(shí)例化的地方入手夜惭,在注釋 5.1 之前的一行代碼看到了?ContextImpl 的實(shí)例化代碼姻灶,跟進(jìn)代碼發(fā)現(xiàn),果不其然诈茧,看到了 mPackageInfo 被賦值的地方:
4. 注釋b.1所在的流程 ?早于 注釋5.4 的(在注釋5.4時(shí)才調(diào)用到了Application的attachBaseContext方法)产喉,在我們自定義的Application中attachBaseContext調(diào)用getApplicationContext方法時(shí),就到了注釋b敢会,而此時(shí)mPackageInfo是不為空的曾沈,所以會(huì)執(zhí)行mPackageInfo.getApplication(),那么我們?cè)倏匆幌翷oadedApk.java中的getApplication方法(正如前面所說走触,mPackageInfo是LoadedApk的實(shí)例)晦譬,
看到這里找到原因所在了:
因?yàn)槲覀冊(cè)?Application 的 attachBaseContext方法 中調(diào)用 getApplicationContext() 時(shí), mApplication 還沒有被賦值互广,所以返回的是空,只有把 attachBaseContext方法 執(zhí)行完成后卧土,mApplication 才會(huì)被賦值惫皱。
附圖一張:
參考
http://androidxref.com
http://blog.csdn.net/u011228356/article/details/45102623
http://www.wtoutiao.com/p/1f8OfGz.html
文章原創(chuàng)作者GuoLin 書籍推薦
郭林大神原創(chuàng)android 書籍:《第一行代碼 android》