Android中動態(tài)替換Application的實現(xiàn)

1.背景

最近一直在做優(yōu)化相關(guān)事情守谓,需要在啟動時干預(yù)加載dex文件的過程犁珠,而AndroidManifest設(shè)定的Application類已經(jīng)在dex文件中缭裆,在加載dex之前颤枪,不能找到這個Application類。所以我們需要替換原有的Application為ProxyApplication嗽桩。使其應(yīng)用啟動時加載ProxyApplication岳守,我們在其中實現(xiàn)加載dex等一些流程處理。而后要替換回原有的Application(以下為RealApplication)碌冶,確保應(yīng)用正常運行,并且要保持生命周期涝缝、初始化順序不變扑庞,屏蔽對于應(yīng)用中g(shù)etContext,getApplicationContext的影響拒逮。

2.需要處理的問題

在我們替換ProxyApplication之后罐氨,在初始化時創(chuàng)建RealApplication,還需要滿意以下幾個條件:

創(chuàng)建RealApplication滩援,維護正常的生命周期栅隐,并進行回調(diào)。

對應(yīng)用中屏蔽掉ProxyApplication,對于下層無感知租悄。在Activity等調(diào)用getApplicationContext之后谨究,應(yīng)該返回RealApplication。

ContentProvider創(chuàng)建時機比較特殊泣棋,在滿足正常的初始化順序之后胶哲,也要屏蔽ProxyApplication的存在。

3.方案具體實現(xiàn)

在AndroidManifest.xml文件中替換Application為ProxyApplication潭辈,可以使用自動化方式鸯屿,或者打包方式,細節(jié)過程這里不做討論把敢。這里主要敘述創(chuàng)建RealApplication的過程寄摆。替換了ProxyApplication之后,對于系統(tǒng)而言ProxyApplication就是應(yīng)用初始化的入口修赞,所有的回調(diào)均是在ProxyApplication中發(fā)生婶恼。我們主要關(guān)注attachBaseContext和onCreate的回調(diào)。

3.1 創(chuàng)建RealApplication

創(chuàng)建RealApplication榔组,我們可以使用反射的方式newInstance創(chuàng)建對象熙尉,然后執(zhí)行回調(diào)attachBaseContext。但是對于不同的系統(tǒng)版本搓扯,內(nèi)部執(zhí)行的細節(jié)可能不同检痰,或者有其它相關(guān)邏輯的處理,所以我們采用另一種方式進行處理锨推。首先看系統(tǒng)源碼的如何實現(xiàn)铅歼,這里選擇8.0.0的系統(tǒng)源碼進行分析,其它版本去http://androidxref.com/查看换可。

我們知道椎椰,Java層初始化可以認為是從android.app.ActivityThread開始,所以從ActivityThread開始查看沾鳄。ActivityThread中存在靜態(tài)方法currentActivityThread返回實例慨飘。

image

ActivityThread內(nèi)部存在成員變量AppBindData mBoundApplication。AppBindData是一個靜態(tài)內(nèi)部類译荞,其中包含成員變量LoadedApk info瓤的。查看android.app.LoadedApk源代碼,發(fā)現(xiàn)創(chuàng)建Application的makeApplication方法吞歼。

image

上面去掉不相關(guān)代碼之后圈膏,可以明顯看出:

如果緩存mApplication不為空,則直接返回篙骡。

mApplication為空時稽坤,則創(chuàng)建RealApplication丈甸,并且執(zhí)行相關(guān)的回調(diào)。創(chuàng)建RealApplication時尿褪,類名是從mApplicationInfo.className中獲取睦擂。

添加新創(chuàng)建RealApplication到mActivityThread.mAllApplications。

賦值給緩存mApplication茫多。

所以我們在調(diào)用makeApplication之前祈匙,需要將mApplication置為null,否則會直接返回ProxyApplication的實例天揖。

首先夺欲,通過android.app.ActivityThread中靜態(tài)方法獲取實例

image

然后,通過ActivityThread實例今膊,獲得LoadedApk實例些阅。為了使makeApplication順利執(zhí)行,先設(shè)置mApplication為null斑唬。移除mAllApplications中ProxyApplication的實例市埋。LoadedApk中mApplicationInfo和AppBindData中appInfo都是ApplicationInfo類型,需要分別替換className字段的值為RealApplication的實際類全名恕刘。

image

之后缤谎,反射調(diào)用系統(tǒng)的makeApplication

image

最終,使用上面三個方法組合起來褐着,完成我們的邏輯坷澡。

image

這樣,在ProxyApplication.attachBaseContext中含蓉,調(diào)用makeApplication創(chuàng)建RealApplication频敛,并且內(nèi)部已經(jīng)完成對于RealApplication的attchBaseContext的回調(diào)。在ProxyApplication.onCreate中只需要回調(diào)RealApplication實例的onCreate馅扣,即可完成對于RealApplication的創(chuàng)建斟赚,已經(jīng)內(nèi)部替換以及正常的生命周期的回調(diào)。而且在Activity中調(diào)用getApplicationContext返回的值差油,實際上也是LoadedApk中mApplication的值拗军,同時也保證對于Activity等地方屏蔽ProxyApplication的目的。

3.2 ContentProvider中g(shù)etContext問題

通過閱讀系統(tǒng)的源代碼(或者自己打log)蓄喇,可以很容易的知道食绿,Application和ContentProvider的初始化順序是:Application.attachBaseContext -> ContentProvider.onCreate -> Application.onCreate

在保持正常Application生命周期的情況下,也要保持對ContentProvider中無感知公罕。因為ContentProvider中也存在getContext方法,看ContentProvider的源代碼實現(xiàn):

image

其中mContext被賦值的有兩個地方耀销,一個在構(gòu)造方法楼眷,一個是attchInfo的時候铲汪。繼續(xù)追蹤源代碼中使用構(gòu)造方法初始化,或者調(diào)用attachInfo的地方罐柳,結(jié)果在android.app.ActivityThread中找到installProvider方法中存在著調(diào)用關(guān)系掌腰。

image

以上源代碼中,省略了不相關(guān)的部分代碼张吉〕萘海可以看出,使用反射調(diào)用ContentProvider無參構(gòu)造方法創(chuàng)建實例肮蛹,然后調(diào)用了attachInfo勺择,傳遞的Context為installProvider方法中的參數(shù)。那這個參數(shù)哪傳遞過來的呢伦忠?帶著疑問從源碼中繼續(xù)追蹤調(diào)用關(guān)系省核,發(fā)現(xiàn)installContentProviders內(nèi)部在初始化每個ContentProvider時,分別調(diào)用了一次installProvider昆码∑遥看installContentProviders的主要邏輯:

image

可以明確,installContentProviders中調(diào)用installProvider時傳遞的Context赋咽,也是由方法調(diào)用時傳遞的參數(shù)旧噪。繼續(xù)向上追蹤發(fā)現(xiàn)ActivityThread.handleBindApplication在初始化ContentProvider時調(diào)用了installContentProviders,看源代碼中關(guān)鍵部分脓匿。

image

通過這部分的源代碼淘钟,我們明確知道,最終通過attachInfo設(shè)置給ContentProvider中的Context的實際類型是Application亦镶。其中返回Application這行語句中日月,data的類型是AppBindData,info的類型是LoadedApk缤骨,所以makeApplication的具體實現(xiàn)就是章節(jié)3.1中列舉的源代碼爱咬。

在App初始化時,系統(tǒng)調(diào)用makeApplication創(chuàng)建了ProxyApplication實例绊起,同時回調(diào)了attachBaseContext(Context context)精拟。所以這個方法返回的就是App初始化時ProxyApplication,調(diào)用發(fā)生ProxyApplication.attachBaseContext之后虱歪,ProxyApplication.onCreate之前蜂绎。所以我們沒有辦法在這兩個方法生命周期內(nèi)進行替換為RealApplication。

如果在attachBaseContext中設(shè)置mInitialApplication的值笋鄙,在初始化ContentProvider之前师枣,又被重新設(shè)置為ProxyApplication。這樣在初始化ContentProvider時傳遞的Context依然是ProxyApplication萧落,在ContentProvider的onCreate調(diào)用getContext返回的依然不是RealApplication践美,這種情況也是不能滿足對下層無感知的需求洗贰。

如果在onCreate中設(shè)置mInitialApplication,也是不能起作用的陨倡,因為此時已經(jīng)完成了ContentProvider的初始化過程敛滋。

仔細閱讀源代碼,發(fā)現(xiàn)初始化ContentProvider是有一系列條件控制兴革,那么我們可以提前修改data.providers為null绎晃,讓系統(tǒng)不執(zhí)行ContentProvider的初始化。我們在ProxyApplication.attachBaseContext主動調(diào)用installContentProviders杂曲,傳遞RealApplication給ContentProvider庶艾。在ProxyApplication.onCreate中恢復(fù)data.providers的數(shù)據(jù),減小對其它邏輯可能帶來的影響解阅。具體的實現(xiàn)分兩部分落竹,在attachBaseContext中調(diào)用我們實現(xiàn)的installContentProviders,源代碼如下:

image

在onCreate中調(diào)用的restoreProviderInfo货抄,恢復(fù)AppBindData中的List<ProviderInfo> providers原來的值述召,具體源代碼如下:

image

4. 總結(jié)

以上大致講解了從源代碼的實現(xiàn)進行分析,了解系統(tǒng)基本的實現(xiàn)原理之后蟹地,進行相關(guān)的反射修改內(nèi)部的值积暖,達到我們替換Application的目的。這種方案怪与,接入成本比較低夺刑,但是新系統(tǒng)出現(xiàn)之后,可能出現(xiàn)兼容性的問題分别,需要每次發(fā)布新系統(tǒng)之后進行相關(guān)的適配遍愿。但是這種解決問題的思路,可以借鑒一下耘斩,從別的地方進行Java Hook沼填,實現(xiàn)一些黑科技的東西。而且替換Application這個方式括授,不只是可以應(yīng)用在安裝速度優(yōu)化上面坞笙,同時也可以應(yīng)用在其他一些場景上。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荚虚,一起剝皮案震驚了整個濱河市薛夜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌版述,老刑警劉巖梯澜,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渴析,居然都是意外死亡腊徙,警方通過查閱死者的電腦和手機简十,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撬腾,“玉大人,你說我怎么就攤上這事恢恼∶裆担” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵场斑,是天一觀的道長漓踢。 經(jīng)常有香客問我,道長漏隐,這世上最難降的妖魔是什么喧半? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮青责,結(jié)果婚禮上挺据,老公的妹妹穿的比我還像新娘。我一直安慰自己脖隶,他們只是感情好扁耐,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著产阱,像睡著了一般婉称。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上构蹬,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天王暗,我揣著相機與錄音,去河邊找鬼庄敛。 笑死俗壹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的铐姚。 我是一名探鬼主播策肝,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼隐绵!你這毒婦竟也來了之众?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤依许,失蹤者是張志新(化名)和其女友劉穎棺禾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峭跳,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡膘婶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年缺前,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悬襟。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡衅码,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脊岳,到底是詐尸還是另有隱情逝段,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布割捅,位于F島的核電站奶躯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏亿驾。R本人自食惡果不足惜嘹黔,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望莫瞬。 院中可真熱鬧儡蔓,春花似錦、人聲如沸乏悄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽檩小。三九已至开呐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間规求,已是汗流浹背筐付。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留阻肿,地道東北人瓦戚。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像丛塌,于是被迫代替她去往敵國和親较解。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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