最近幾年移動開發(fā)業(yè)界興起了「 插件化技術(shù) 」的旋風(fēng),各個大廠都推出了自己的插件化框架,各種開源框架都評價自身功能優(yōu)越性拥褂,令人目不暇接。隨著公司業(yè)務(wù)快速發(fā)展牙寞,項(xiàng)目增多饺鹃,開發(fā)資源卻有限,如何能在有限資源內(nèi)滿足需求和項(xiàng)目的增長碎税,同時又能快速響應(yīng)問題和迭代新需求尤慰,這就是一個矛盾點(diǎn)。此時雷蹂,插件化技術(shù)正好風(fēng)生水起伟端,去了解各個主流框架實(shí)現(xiàn)思路,看看能對目前工作是否有幫助匪煌,是很有必要的责蝠。
主要分為以下幾個部分:
·插件化介紹
·入門知識
·實(shí)現(xiàn)原理
·主流框架
·實(shí)戰(zhàn)
·小結(jié)
·進(jìn)階資料
插件化介紹
百度百科里是這么定義插件的:「 是一種遵循一定規(guī)范的應(yīng)用程序接口編寫出來的程序,只能運(yùn)行在程序規(guī)定的系統(tǒng)平臺下萎庭,而不能脫離指定的平臺單獨(dú)運(yùn)行霜医。」驳规,也就是說肴敛,插件可以提供一種動態(tài)擴(kuò)展能力,使得應(yīng)用程序在運(yùn)行時加載原本不屬于該應(yīng)用的功能吗购,并且做到動態(tài)更新和替換医男。
那么在 Android 中,何為「 插件化 」捻勉,顧名思義镀梭,就是把一些核心復(fù)雜依賴度高的業(yè)務(wù)模塊封裝成獨(dú)立的插件,然后根據(jù)不同業(yè)務(wù)需求進(jìn)行不同組合踱启,動態(tài)進(jìn)行替換报账,可對插件進(jìn)行管理研底、更新,后期對插件也可進(jìn)行版本管理等操作透罢。在插件化中有兩個概念需要講解下:
宿主
所謂宿主榜晦,就是需要能提供運(yùn)行環(huán)境,給資源調(diào)用提供上下文環(huán)境琐凭,一般也就是我們主 APK 芽隆,要運(yùn)行的應(yīng)用,它作為應(yīng)用的主工程所在统屈,實(shí)現(xiàn)了一套插件的加載和管理的框架胚吁,插件都是依托于宿主的APK而存在的。
插件
插件可以想象成每個獨(dú)立的功能模塊封裝為一個小的 APK 愁憔,可以通過在線配置和更新實(shí)現(xiàn)插件 APK 在宿主 APK 中的上線和下線腕扶,以及動態(tài)更新等功能。
那么為何要使用插件化技術(shù)吨掌,它有何優(yōu)勢半抱,能給我們帶來什么樣好處,這里簡單列舉了以下幾點(diǎn):
·讓用戶不用重新安裝 APK 就能升級應(yīng)用功能膜宋,減少發(fā)版本頻率窿侈,增加用戶體驗(yàn)。
·提供一種快速修復(fù)線上 BUG 和更新的能力秋茫。
·按需加載不同的模塊史简,實(shí)現(xiàn)靈活的功能配置,減少服務(wù)器對舊版本接口兼容壓力肛著。
·模塊化圆兵、解耦合、并行開發(fā)枢贿、 65535 問題殉农。
入門知識
首先我們要知道插件化技術(shù)是屬于比較復(fù)雜一個領(lǐng)域压语,復(fù)雜點(diǎn)在于它涉及知識點(diǎn)廣泛尤仍,不僅僅是上層做應(yīng)用架構(gòu)能力,還要求我們對 Android 系統(tǒng)底層知識需要有一定的認(rèn)知怠惶,這里簡單羅列了其中會涉及的知識點(diǎn):
首先耀态,要介紹的是 Binder 轮傍,我們都知道 Android 多進(jìn)程通信核心就是 Binder ,如果沒有它真的寸步難行茫陆。 Binder 涉及兩層技術(shù)金麸,你可以認(rèn)為它是一個中介者模式擎析,在客戶端和服務(wù)器端之間簿盅, Binder 就起到中介的作用挥下。如果要實(shí)現(xiàn)四大組件的插件化,就需要在 Binder 上做修改桨醋, Binder 服務(wù)端的內(nèi)容沒辦法修改棚瘟,只能改客戶端的代碼,而且四大組件的每個組件的客戶端都不一樣喜最,這個就需要深入研究了偎蘸。學(xué)習(xí)Binder的最好方式是 AIDL ,這方面在網(wǎng)上有很多資料瞬内,最簡單的方式就是自己寫個 aidl 文件自動生成一個 Java 類迷雪,然后去查看這個Java類的每個方法和變量,然后再去看四大組件虫蝶,其實(shí)都是跟 AIDL 差不多的實(shí)現(xiàn)方式章咧。
其次,是 App 打包的流程能真。代碼寫完了赁严,執(zhí)行一次打包操作,中途經(jīng)歷了資源打包粉铐、 Dex 生成疼约、簽名等過程。其中最重要的就是資源的打包蝙泼,即 AAPT 這一步程剥,如果宿主和插件的資源id沖突,一種解決辦法就是在這里做修改踱承。
第三倡缠, App 在手機(jī)上的安裝流程也很重要。熟悉安裝流程不僅對插件化有幫助茎活,在遇到安裝 Bug 的時候也非常重要昙沦。手機(jī)安裝 App 的時候,經(jīng)常會有下載異常载荔,提示資源包不能解析盾饮,這時需要知道安裝 App 的這段代碼在什么地方,這只是第一步懒熙。第二步需要知道丘损, App 下載到本地后,具體要做哪些事情工扎。手機(jī)有些目錄不能訪問徘钥, App 下載到本地之后,放到哪個目錄下肢娘,然后會生成哪些文件呈础。插件化有個增量更新的概念舆驶,如何下載一個增量包,從本地具體哪個位置取出一個包而钞,這個包的具體命名規(guī)則是什么沙廉,等等。這些細(xì)節(jié)都必須要清楚明白臼节。
第四撬陵,是 App 的啟動流程。 Activity 啟動有幾種方式网缝?一種是寫一個 startActivity 巨税,第二種是點(diǎn)擊手機(jī) App ,通過手機(jī)系統(tǒng)里的 Launcher 機(jī)制粉臊,啟動 App 里默認(rèn)的 Activity 垢夹。通常, App 開發(fā)人員喜聞樂見的方式是第二種维费。那么第一種方式的啟動原理是什么呢果元?另外,啟動的時候犀盟,Main 函數(shù)在哪里而晒?這個 Main 函數(shù)的位置很重要,我們可以對它所在的類做修改阅畴,從而實(shí)現(xiàn)插件化倡怎。
第五點(diǎn)更重要,做 Android 插件化需要控制兩個地方贱枣。首先是插件 Dex 的加載监署,如何把插件 Dex 中的類加載到內(nèi)存?另外是資源加載的問題纽哥。插件可能是 Apk 也可能是 so 格式钠乏,不管哪一種,都不會生成 R.id 春塌,從而沒辦法使用晓避。這個問題有好幾種解決方案。一種是是重寫 Context 的 getAsset 只壳、 getResource 之類的方法俏拱,偷換概念,讓插件讀取插件里的資源吼句,但缺點(diǎn)就是宿主和插件的資源 id 會沖突锅必,需要重寫 AAPT 。另一種是重寫 AMS中保存的插件列表惕艳,從而讓宿主和插件分別去加載各自的資源而不會沖突搞隐。第三種方法分蓖,就是打包后,執(zhí)行一個腳本尔许,修改生成包中資源id。
第六點(diǎn)终娃,在實(shí)施插件化后味廊,如何解決不同插件的開發(fā)人員的工作區(qū)問題。比如棠耕,插件1和插件2余佛,需要分別下載哪些代碼,如何獨(dú)立運(yùn)行窍荧?就像機(jī)票和火車票辉巡,如何只運(yùn)行自己的插件,而不運(yùn)行別人的插件蕊退?這是協(xié)同工作的問題郊楣。火車票和機(jī)票瓤荔,這兩個 Android 團(tuán)隊(duì)的各自工作區(qū)是不一樣的净蚤,這時候就要用到 Gradle 腳本了,每個項(xiàng)目分別有各自的倉庫输硝,有各自不同的打包腳本今瀑,只需要把自己的插件跟宿主項(xiàng)目一起打包運(yùn)行起來,而不用引入其他插件点把,還有更厲害的是橘荠,也可以把自己的插件當(dāng)作一個 App 來打包并運(yùn)行。
上面介紹了插件化的入門知識郎逃,一共六點(diǎn)哥童,每一點(diǎn)都需要花大量時間去理解。否則褒翰,在面對插件化項(xiàng)目的時候如蚜,很多地方你會一頭霧水。而只要理解了這六點(diǎn)核心影暴,一切可迎刃而解错邦。
實(shí)現(xiàn)原理
在Android中應(yīng)用插件化技術(shù),其實(shí)也就是動態(tài)加載的過程型宙,分為以下幾步:
·把可執(zhí)行文件( .so/dex/jar/apk 等)拷貝到應(yīng)用 APP 內(nèi)部撬呢。
·加載可執(zhí)行文件,更換靜態(tài)資源
·調(diào)用具體的方法執(zhí)行業(yè)務(wù)邏輯
Android 項(xiàng)目中妆兑,動態(tài)加載技術(shù)按照加載的可執(zhí)行文件的不同大致可以分為兩種:
·動態(tài)加載 .so 庫
·動態(tài)加載 dex/jar/apk文件(現(xiàn)在動態(tài)加載普遍說的是這種)
第一點(diǎn)魂拦, Android 中 NDK 中其實(shí)就使用了動態(tài)加載毛仪,動態(tài)加載 .so 庫并通過 JNI 調(diào)用其封裝好的方法。后者一般是由 C/C++ 編譯而成芯勘,運(yùn)行在 Native 層箱靴,效率會比執(zhí)行在虛擬機(jī)層的 Java 代碼高很多,所以 Android 中經(jīng)常通過動態(tài)加載 .so 庫來完成一些對性能比較有需求的工作(比如 Bitmap 的解碼荷愕、圖片高斯模糊處理等)衡怀。此外,由于 .so 庫是由 C/C++ 編譯而來的安疗,只能被反編譯成匯編代碼抛杨,相比中 dex 文件反編譯得到的 Smali 代碼更難被破解,因此 .so 庫也可以被用于安全領(lǐng)域荐类。
其二怖现,“基于 ClassLoader 的動態(tài)加載 dex/jar/apk 文件”,就是我們指在 Android 中 動態(tài)加載由 Java 代碼編譯而來的 dex 包并執(zhí)行其中的代碼邏輯玉罐,這是常規(guī) Android 開發(fā)比較少用到的一種技術(shù)屈嗤,目前說的動態(tài)加載指的就是這種。
Android 項(xiàng)目中吊输,所有 Java 代碼都會被編譯成 dex 文件恢共,Android 應(yīng)用運(yùn)行時,就是通過執(zhí)行 dex 文件里的業(yè)務(wù)代碼邏輯來工作的璧亚。使用動態(tài)加載技術(shù)可以在 Android 應(yīng)用運(yùn)行時加載外部的 dex 文件讨韭,而通過網(wǎng)絡(luò)下載新的 dex 文件并替換原有的 dex 文件就可以達(dá)到不安裝新 APK 文件就升級應(yīng)用(改變代碼邏輯)的目的。
所以說癣蟋,在 Android 中的 ClassLoader 機(jī)制主要用來加載 dex 文件透硝,系統(tǒng)提供了兩個 API 可供選擇:
PathClassLoader:只能加載已經(jīng)安裝到 Android 系統(tǒng)中的 APK 文件。因此不符合插件化的需求疯搅,不作考慮濒生。
DexClassLoader:支持加載外部的 APK、Jar 或者 dex 文件幔欧,正好符合文件化的需求罪治,所有的插件化方案都是使用 DexClassloader 來加載插件 APK 中的 .class文件的。
主流框架
在 Android 中實(shí)現(xiàn)插件化框架礁蔗,需要解決的問題主要如下:
·資源和代碼的加載
·Android 生命周期的管理和組件的注冊
·宿主 APK 和插件 APK 資源引用的沖突解決
·下面分析幾個目前主流的開源框架觉义,看看每個框架具體實(shí)現(xiàn)思路和優(yōu)缺點(diǎn)。
DL 動態(tài)加載框架 ( 2014 年底)
是基于代理的方式實(shí)現(xiàn)插件框架浴井,對 App 的表層做了處理晒骇,通過在 Manifest 中注冊代理組件,當(dāng)啟動插件組件時,首先啟動一個代理組件洪囤,然后通過這個代理組件來構(gòu)建徒坡,啟動插件組件。 需要按照一定的規(guī)則來開發(fā)插件 APK瘤缩,插件中的組件需要實(shí)現(xiàn)經(jīng)過改造后的 Activity喇完、FragmentActivity、Service 等的子類剥啤。
優(yōu)點(diǎn)如下:
插件需要遵循一定的規(guī)則锦溪,因此安全方面可控制。
方案簡單铐殃,適用于自身少量代碼的插件化改造。
缺點(diǎn)如下:
不支持通過 This 調(diào)用組件的方法跨新,需要通過 that 去調(diào)用富腊。
由于 APK 中的 Activity 沒有注冊,不支持隱式調(diào)用 APK 內(nèi)部的 Activity域帐。
插件編寫和改造過程中赘被,需要考慮兼容性問題比較多,聯(lián)調(diào)起來會比較費(fèi)時費(fèi)力肖揣。
DroidPlugin ( 2015 年 8 月)
DroidPlugin 是 360 手機(jī)助手實(shí)現(xiàn)的一種插件化框架民假,它可以直接運(yùn)行第三方的獨(dú)立 APK 文件,完全不需要對 APK 進(jìn)行修改或安裝龙优。一種新的插件機(jī)制羊异,一種免安裝的運(yùn)行機(jī)制,是一個沙箱(但是不完全的沙箱彤断。就是對于使用者來說野舶,并不知道他會把 apk 怎么樣), 是模塊化的基礎(chǔ)宰衙。
實(shí)現(xiàn)原理:
共享進(jìn)程:為android提供一個進(jìn)程運(yùn)行多個 apk 的機(jī)制平道,通過 API 欺騙機(jī)制瞞過系統(tǒng)。
占坑:通過預(yù)先占坑的方式實(shí)現(xiàn)不用在 manifest 注冊供炼,通過一帶多的方式實(shí)現(xiàn)服務(wù)管理一屋。
Hook 機(jī)制:動態(tài)代理實(shí)現(xiàn)函數(shù) hook ,Binder 代理繞過部分系統(tǒng)服務(wù)限制袋哼,IO 重定向(先獲取原始 Object –> Read 冀墨,然后動態(tài)代理 Hook Object 后–> Write 回去,達(dá)到瞞天過海的目的)涛贯。
插件 Host 的程序架構(gòu):
優(yōu)點(diǎn)如下:
支持 Android 四大組件轧苫,而且插件中的組件不需要在宿主 APK 中注冊。
支持 Android 2.3 及以上系統(tǒng),支持所有的系統(tǒng) API含懊。
插件與插件之間身冬,插件與宿主之間的代碼和資源完全隔閡。
實(shí)現(xiàn)了進(jìn)程管理岔乔,插件的空進(jìn)程會被及時回收酥筝,占用內(nèi)存低。
缺點(diǎn)如下:
插件 APK 中不支持自定義資源的 Notification雏门,通知欄限制嘿歌。
插件 APK 中無法注冊具有特殊的 IntentFilter 的四大組件。
缺乏對 Native 層的 Hook 操作茁影,對于某些帶有 Native 代碼的插件 APK 支持不友好宙帝,可能無法正常運(yùn)行。
由于插件與插件募闲,插件與宿主之間的代碼完全隔離步脓,因此,插件與插件浩螺,插件與宿主之間的通信只能通過 Android 系統(tǒng)級別的通信方式靴患。
安全性擔(dān)憂(可以修改,hook一些重要信息)要出。
機(jī)型適配(不是所有機(jī)器上都能行鸳君,因?yàn)榇罅坑梅瓷湎嚓P(guān),如果rom廠商深度定制了framework層患蹂,反射的方法或者類不在或颊,容易插件運(yùn)用失敗)
Small ( 2015 年底)
Small 是一種實(shí)現(xiàn)輕巧的跨平臺插件化框架传于,基于“輕量饭宾、透明、極小化格了、跨平臺”的理念看铆,實(shí)現(xiàn)原理有以下三點(diǎn):
動態(tài)加載類:我們知道插件化很多都從 DexClassLoader 類有個 DexPathList 清單,支持 dex/jar/zip/apk 文件格式盛末,卻沒有支持 .so 文件格式弹惦,因此 Small 框架則是把 .so 文件包裝成 zip 文件格式,插入到 DexPathList 集合中悄但,改寫動態(tài)加載的代碼棠隐。
資源分段:由于 Android 資源的格式是 0xPPTTNNNN ,PP 是包 ID 檐嚣,00-02 是屬于系統(tǒng)助泽,7f 屬于應(yīng)用程序啰扛,03-7e 則保留,可以在這個范圍內(nèi)做文章 嗡贺, TT 則是 Type 比如隐解,attr 、layout 诫睬、string 等等煞茫,NNNN 則是資源全局 ID。那么這個框架則是對資源包進(jìn)行重新打包摄凡,每個插件重新分配資源 ID 续徽,這樣就保證了宿主和插件的資源不沖突。
動態(tài)代理注冊:在 Android 中要使用四大組件亲澡,都是需要在 manifest 清單中注冊钦扭,這樣才可以使用,那如何在不注冊情況也能使用呢床绪,這里就是用到動態(tài)代理機(jī)制進(jìn)行 Hook 客情,在發(fā)送 AMS 之前用占坑的組件來欺騙系統(tǒng),通過認(rèn)證后会涎,再把真正要調(diào)用的組件還原回來裹匙,達(dá)到瞞天過海目的瑞凑。
架構(gòu)圖:
優(yōu)點(diǎn)如下:
所有插件支持內(nèi)置宿主包中末秃。
插件的編碼和資源文件的使用與普通開發(fā)應(yīng)用沒有差別。
通過設(shè)定 URI 籽御,宿主以及 Native 應(yīng)用插件练慕,Web 插件,在線網(wǎng)頁等能夠方便進(jìn)行通信技掏。
支持 Android 铃将、 iOS 、和 Html5 哑梳,三者可以通過同一套 Javascript 接口實(shí)現(xiàn)通信劲阎。
缺點(diǎn)如下:
暫不支持 Service 的動態(tài)注冊,不過這個可以通過將 Service 預(yù)先注冊在宿主的 AndroidManifest.xml 文件中進(jìn)行規(guī)避鸠真,因?yàn)?Service 的更新頻率通常非常低悯仙。
框架對比:
小結(jié)
正如開頭所說,要實(shí)現(xiàn)插件化的框架吠卷,無非就是解決那典型的三個問題:插件代碼如何加載锡垄、插件中的組件生命周期如何管理、插件資源和宿主資源沖突怎么辦祭隔。每個框架針對這三個問題货岭,都有不同的解決方案,同時呢,根據(jù)時間順序千贯,后出來的框架往往都會吸收已經(jīng)出的框架精髓屯仗,進(jìn)而修復(fù)那些比較有里程碑意義框架的不足。但這些框架的核心思想都是用到了代理模式丈牢,有的在表面層進(jìn)行代理祭钉,有的則在系統(tǒng)應(yīng)用層進(jìn)行代理,通過代理達(dá)到替換和瞞天過海己沛,最終讓 Android 系統(tǒng)誤以為調(diào)用插件功能和調(diào)用原生開發(fā)的功能是一樣的慌核,進(jìn)而達(dá)到插件化和原生兼容編程的目的。
進(jìn)階資料
1申尼,Android插件化從入門到放棄-最強(qiáng)合集
2垮卓,包建強(qiáng)的無線技術(shù)空間,寫給Android App 開發(fā)人員看的 Android 底層知識 置頂8篇
3师幕,Android插件化原理解析