Android插件化技術(shù)廣泛應(yīng)用在移動開發(fā)中,通過插件化技術(shù),AndroidApp可以不通過發(fā)布新版本而修復(fù)線上的bug或者推出新功能,這對于日活千萬的App而言是非常重要的,可極大程度提升App開發(fā)效率和質(zhì)量。本書詳細(xì)介紹了插件化技術(shù)的原理、各種方案,及其在AndroidApp中的使用。在介紹插件化技術(shù)的同時,還詳細(xì)介紹了Android底層知識,能幫助AndroidApp開發(fā)人員更好地掌握Android的開發(fā)技術(shù)。本書共22章,先介紹Android插件化技術(shù)的歷史和現(xiàn)狀,系統(tǒng)介紹了Android的底層知識以及反射、代理等技術(shù);然后介紹插件化技術(shù)的基本知識,包括各類插件化解決方案及其對比;之后分析了一些插件化框架,如that、Zeus等,深入分析了資源及其在插件化中的應(yīng)用,以及混淆、增量更新等周邊技術(shù)的插件化解決方案。
本書不僅詳細(xì)介紹Android插件化技術(shù)如何實(shí)現(xiàn),而且包含大量Android系統(tǒng)的底層知識,有助于App開發(fā)人員深入理解Android系統(tǒng),從而寫出更健壯的代碼。Android插件化技術(shù)不僅適用于快速修復(fù)bug,還可以快速上線新功能,從而在時間上和競爭對手搶占用戶。本書詳細(xì)介紹了插件化技術(shù)的各種方案,及其實(shí)現(xiàn)代碼,并給出了應(yīng)用場景。
前 言 Preface這是一本什么書如果只把本書當(dāng)作純粹介紹Android插件化技術(shù)的書那就錯了。本書在研究Android插件化之余,還詳細(xì)介紹了Android系統(tǒng)的底層知識,包括Binder和AIDL的原理、四大組件的原理、App的安裝和啟動流程、Context和ClassLoader的家族史。沒有羅列大量的Android系統(tǒng)中的源碼,而是以一張張UML圖把這些知識串起來。
本書詳細(xì)介紹了Android中的資源機(jī)制,包括aapt命令的原理、resource文件的組成以及public.xml的使用方式,順帶還提及了如何自定義一個Gradle插件化。
此外,本書還介紹了so的加載原理,尤其是動態(tài)加載so的技術(shù),可以幫助App進(jìn)行瘦身;探討了HTML5降級技術(shù),可以實(shí)現(xiàn)任何一個原生頁面和HTML5頁面的互換;介紹了反射技術(shù),以及jOOR這個有趣的開源框架;介紹了Android中的動態(tài)代理技術(shù)Proxy.newProxyInstance方法。
如果讀者能堅(jiān)持把這本書從頭到尾讀完,那么不僅掌握了插件化技術(shù),而且也把上述所有這些知識點(diǎn)全都系統(tǒng)地學(xué)習(xí)了一遍。也許Android插件化會隨著Google的限制而有所變化甚至消亡,但我在本書中介紹的其他知識,仍然是大有用武之處的。
如何面對Android P的限制寫作這本書的時候,Google推出了Android P preview的操作系統(tǒng),會限制對@hide api的反射調(diào)用。目前會通過log發(fā)出警告,用戶代碼仍然能夠獲取到正確的Method或Field,在后續(xù)版本中獲取到的Method或Field極有可能為空。
但是道高一尺,魔高一丈。Google對這次限制,很快就被技術(shù)極客們繞過去了,有兩種解決方法:
1)把通過反射調(diào)用的系統(tǒng)內(nèi)部類改為直接調(diào)用。具體操作辦法是,在Android項(xiàng)目中新建一個庫,把要反射的類的方法和字段復(fù)制一份到這個庫中,App對這個庫的引用關(guān)系設(shè)置為provided。那么我們就可以在App中直接調(diào)用這個類和方法,同時,在編譯的時候,又不會把這些類包含到apk中。
其實(shí)早在2015年,hoxkx就在他的插件化框架中實(shí)現(xiàn)了這種技術(shù)。但是這種解決方案,僅限于Android系統(tǒng)中標(biāo)記為public的方法和字段,對于protected和private就無能為力了。比如AssetsManager的addAssetPath方法,ActivityThread的currentActivityThread方法。
2)類的每個方法和字段都有一個標(biāo)記,表明它是不是hide類型的。我們只要在jni層,把這個標(biāo)記改為不是hide的,就可以繞過檢查了。
然而,魔高一丈,道高一丈二。Google在Android P的正式版中勢必會推出更嚴(yán)厲的限制方案,到時候,又會有新的解決方案面世,讓我們拭目以待。
其實(shí),開發(fā)者是無意和Google進(jìn)行技術(shù)對抗的,這是毫無意義的。泛濫成災(zāi)的修改導(dǎo)致了App大量的崩潰,Google實(shí)在看不下去了,所以才搞出這套限制方案;另一方面,插件化技術(shù)是剛需,尤其在中國的互聯(lián)網(wǎng)行業(yè),App崩潰會直接影響使用,很可能導(dǎo)致經(jīng)濟(jì)損失,所以開發(fā)者才會不惜一切代價走插件化這條路。
再回到限制方案來,Google也不是清一色不要開發(fā)者使用系統(tǒng)底層的標(biāo)記為hide的API,而是推出了一組黑灰名單,如下所示:
名 單影 響light-greylist 淺灰名單僅打印警告日志,Google盡可能在未來版本提供 public APIdark-greylist 深灰名單第三方App不能訪問,開發(fā)者可以申請把這份清單中的某些API加入到淺灰名單blacklist 黑名單第三方App不能訪問所以,另一種應(yīng)對策略是,在插件化中使用淺灰名單中的API,比如說ActivityThread的currentActivityThread方法。
Google的這組清單還在持續(xù)調(diào)整中,據(jù)我所知,給各大手機(jī)廠商的清單與其在社區(qū)中發(fā)布的清單略有出入。在Android P的正式版本中,這份清單會最終確定下來。所以現(xiàn)在中國的各個插件化框架的開發(fā)人員,都在等Android P的正式版本發(fā)布后再制定相應(yīng)的策略。留給中國隊(duì)的時間不多了。
這本書的來龍去脈這是一本醞釀了3年的書。早在2015年Android插件化技術(shù)百家爭鳴時,我就看好這個技術(shù),想寫一本書介紹這個技術(shù),但當(dāng)時的積累還不夠。那年,我在一場技術(shù)大會上發(fā)表了《Android插件化從入門到放棄》演講,四十五分鐘介紹了插件化技術(shù)的皮毛。后來這個演講內(nèi)容被整理成文章發(fā)布到網(wǎng)上,流傳很廣。
2017年1月,有企業(yè)要我去講2天Android插件化技術(shù)。為此,我花了一個月時間,準(zhǔn)備了四十多個例子。這是我第一次系統(tǒng)地積累了素材。
2017年6月,我在騰訊課堂做Android線上培訓(xùn),為了宣傳推廣我的課程,我寫了一系列文章《寫給Android App開發(fā)人員看的Android底層知識》,共8篇,沒列太多代碼,完全以UML圖的方式向讀者普及Binder、AIDL、四大組件、AMS、PMS的知識。本書的第2章就是在這8篇文章的基礎(chǔ)之上進(jìn)行擴(kuò)充的。
2018年1月,我父親住院一周。我當(dāng)時在醫(yī)院每天晚上值班。老爺子半夜打呼嚕,吵得我睡不著,事后我才知道,我睡著了打呼嚕聲音比他還大。半夜睡不著時就開始了本書的寫作,每晚堅(jiān)持寫到凌晨兩三點(diǎn)。直到父親出院,這本書寫了將近五分之一。
碰巧的是,這一年5月底我結(jié)婚,促使我想在5月初完成這本書的一稿,為此,我宅在家里整整寫了3個月。僅以此書作為新婚禮物獻(xiàn)給我親愛的老婆,感謝你的理解,這本書才得以面世。
作者簡介包建強(qiáng) 畢業(yè)于復(fù)旦大學(xué)數(shù)學(xué)系。先后在多家互聯(lián)網(wǎng)公司擔(dān)任無線部門技術(shù)總監(jiān),現(xiàn)在從事區(qū)塊鏈技術(shù)領(lǐng)域的研究,在Android、iOS、ReactNative等多門無線技術(shù)中跋涉過,在App的項(xiàng)目管理上也有多年的實(shí)踐經(jīng)驗(yàn)。他曾經(jīng)出版了《App研發(fā)錄》,并有一個堅(jiān)持寫了10年的技術(shù)博客:http://jax.cnblogs.com/,他的GitHub地址:https://github.com/BaoBaoJianqiang。
目錄 Contents
序一
序二
序三
前言
第一部分 預(yù)備知識
第1章 插件化技術(shù)的昨天、今天與明天2
1.1 插件化技術(shù)是什么2
1.2 為什么需要插件化3
1.3 插件化技術(shù)的歷史3
1.4 插件化技術(shù)的用途到底是什么8
1.5 更好的替代品:React Native8
1.6 只有中國這么玩嗎9
1.7 四大組件都需要插件化技術(shù)嗎10
1.8 雙開和虛擬機(jī)10
1.9 從原生頁面到HTML 5的過渡11
1.10 本章小結(jié)12
第2章 Android底層知識13
2.1 概述13
2.2 Binder原理14
2.3 AIDL原理16
2.4 AMS20
2.5 Activity工作原理21
2.5.1 App是怎么啟動的21
2.5.2 啟動App并非那么簡單21
2.6 App內(nèi)部的頁面跳轉(zhuǎn)32
2.7 Context家族史34
2.8 Service工作原理36
2.8.1 在新進(jìn)程啟動Service36
2.8.2 啟動同一進(jìn)程的Service39
2.8.3 在同一進(jìn)程綁定Service39
2.9 BroadcastReceiver工作原理41
2.9.1 注冊過程43
2.9.2 發(fā)送廣播的流程44
2.9.3 廣播的種類45
2.10 ContentProvider工作原理46
2.10.1 ContentProvider的本質(zhì)49
2.10.2 匿名共享內(nèi)存(ASM)49
2.10.3 ContentProvider與AMS的通信流程50
2.11 PMS及App安裝過程52
2.11.1 PMS簡介52
2.11.2 App的安裝流程52
2.11.3 PackageParser53
2.11.4 ActivityThread與PackageManager54
2.12 ClassLoader家族史55
2.13 雙親委托57
2.14 MultiDex57
2.15 實(shí)現(xiàn)一個音樂播放器App59
2.15.1 基于兩個Receiver的音樂播放器59
2.15.2 基于一個Receiver的音樂播放器63
2.16 本章小結(jié)68
第3章 反射70
3.1 基本反射技術(shù)70
3.1.1 根據(jù)一個字符串得到一個類70
3.1.2 獲取類的成員71
3.1.3 對泛型類的反射75
3.2 jOOR77
3.2.1 根據(jù)一個字符串得到一個類78
3.2.2 獲取類的成員78
3.2.3 對泛型類的反射79
3.3 對基本反射語法的封裝80
3.3.1 反射出一個構(gòu)造函數(shù)81
3.3.2 調(diào)用實(shí)例方法81
3.3.3 調(diào)用靜態(tài)方法82
3.3.4 獲取并設(shè)置一個字段的值82
3.3.5 對泛型類的處理83
3.4 對反射的進(jìn)一步封裝84
3.5 本章小結(jié)88
第4章 代理模式89
4.1 概述89
4.1.1 遠(yuǎn)程代理(AIDL)90
4.1.2 保護(hù)代理(權(quán)限控制)92
4.1.3 虛代理(圖片占位)92
4.1.4 協(xié)作開發(fā)(Mock Class)92
4.1.5 給生活加點(diǎn)料(記日志)93
4.2 靜態(tài)代理和動態(tài)代理94
4.3 對AMN的Hook95
4.4 對PMS的Hook97
4.5 本章小結(jié)98
第5章 對startActivity方法進(jìn)行Hook99
5.1 startActivity方法的兩種形式99
5.2 對Activity的startActivity方法進(jìn)行Hook100
5.2.1 方案1:重寫Activity的startActivityForResult方法102
5.2.2 方案2:對Activity的mInstrumentation字段進(jìn)行Hook102
5.2.3 方案3:對AMN的getDefault方法進(jìn)行Hook104
5.2.4 方案4:對H類的mCallback字段進(jìn)行Hook107
5.2.5 方案5:再次對Instrumentation字段進(jìn)行Hook109
5.3 對Context的startActivity方法進(jìn)行Hook111
5.3.1 方案6:對ActivityThread的mInstrumentation字段進(jìn)行Hook111
5.3.2 對AMN的getDafault方法進(jìn)行Hook是一勞永逸的113
5.4 啟動沒有在AndroidManifest中聲明的Activity113
5.4.1 欺騙AMS的策略分析114
5.4.2 Hook的上半場115
5.4.3 Hook的下半場:對H類的mCallback字段進(jìn)行Hook118
5.4.4 Hook的下半場:對ActivityThread的mInstrumentation字段進(jìn)行Hook119
5.4.5 欺騙AMS的弊端121
5.5 本章小結(jié)121
第二部分 解決方案
第6章 插件化技術(shù)基礎(chǔ)知識124
6.1 加載外部的dex124
6.2 面向接口編程126
6.3 插件的瘦身129
6.4 對插件進(jìn)行代碼調(diào)試131
6.5 Application的插件化解決方案133
6.6 本章小結(jié)134
第7章 資源初探135
7.1 資源加載機(jī)制135
7.1.1 資源分類135
7.1.2 剪不斷理還亂:Resources和AssetManager136
7.2 資源的插件化解決方案137
7.3 換膚141
7.4 殊途同歸:另一種換膚方式149
7.5 本章小結(jié)149
第8章 最簡單的插件化解決方案150
8.1 在AndroidManifest中聲明插件中的組件150
8.2 宿主App加載插件中的類151
8.3 啟動插件Service152
8.4 加載插件中的資源152
8.5 本章小結(jié)154
第9章 Activity的插件化解決方案155
9.1 啟動沒有在AndroidManifest中聲明的插件Activity155
9.2 基于動態(tài)替換的Activity插件化解決方案159
9.2.1 Android啟動Activity的原理分析159
9.2.2 故意命中緩存160
9.2.3 加載插件中類的方案1:為每個插件創(chuàng)建一個ClassLoader164
9.2.4 為了圓一個謊言而編造更多的謊言164
9.3 加載插件中類的方案2:合并多個dex166
9.4 為Activity解決資源問題169
9.5 對LaunchMode的支持169
9.6 加載插件中類的方案3:修改App原生的ClassLoader172
9.7 本章小結(jié)174
第10章 Service的插件化解決方案175
10.1 Android界的荀彧和荀攸:Service和Activity175
10.2 預(yù)先占位176
10.3 startService的解決方案178
10.4 bindService的解決方案183
10.5 本章小結(jié)185
第11章 BroadcastReceiver的插件化解決方案186
11.