Application和四大組件啟動(dòng)時(shí)的方法順序和相關(guān)注意事項(xiàng)

作者簡(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》

淘寶鏈接: https://s.click.taobao.com/t?e=m%3D2%26s%3DgKUfuKdAZKocQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67p2n%2BQBNMyE6Rku8%2Bpj6eJall3bs%2B3NRhNHnsKI%2BqxhyM0iVZhTFBom4YIorMPnmg8G0g2OJi%2FzmXHfenomYtn5EW9vzeG8LzfPUwktUBEmkxg5p7bh%2BFbQ%3D&pvid=10_106.6.161.154_3367_1490163222155

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容