現(xiàn)在網(wǎng)絡(luò)上有許多關(guān)于動態(tài)加載的介紹的文章,談及的關(guān)鍵詞匯有動態(tài)加載
返劲、插件化
玲昧、熱部署
、熱修復(fù)
等篮绿,對于一些剛接觸這方面開發(fā)技術(shù)的人來說孵延,可能容易混淆。
動態(tài)加載的類型
無論是插件化亲配、熱部署還是熱修復(fù)尘应,這些技術(shù)的根源都可是說是動態(tài)加載,這也是我把 “動態(tài)加載” 作為這個系列文章主題的原因吼虎。
動態(tài)加載犬钢,就是在程序運(yùn)行時,加載外部的可執(zhí)行文件并運(yùn)行思灰。這里的運(yùn)行時就是指應(yīng)用冷啟動并開始工作后玷犹;外部可以是可以是 SD 卡,可以是 data 目錄官辈,也可以是 jniLib 目錄箱舞,這些可執(zhí)行文件是沒有隨著應(yīng)用一起編譯的。
Android 的動態(tài)加載按照工作機(jī)制的不同拳亿,可以分為虛擬機(jī)層動態(tài)加載
和 Native 層動態(tài)加載
兩大類晴股。
運(yùn)行在虛擬機(jī)
簡單來說就是只用 JAVA 代碼搞定的類型。
基于虛擬機(jī)的動態(tài)加載技術(shù)的核心是類加載器 ClassLoader肺魁,通過它我們能夠加載一些新的類电湘,這種方式也是目前大部分技術(shù)文章談到的加載方式
。其中鹅经,根據(jù) ClassLoader 使用方式的不同寂呛,又演變出 “熱部署”、“插件化”瘾晃、“熱修復(fù)” 等技術(shù)贷痪。
熱部署
加載外部可執(zhí)行文件的 ClassLoader 實例與原 APP 的 ClassLoader 實例是互相獨立的(不在同一棵代理樹上),加載進(jìn)來的新的類與原 APP(宿主)里存在的類互相獨立蹦误,根據(jù) Java 對類的定義劫拢,因為這些類的 ClassLoader 不同,所以他們即便包名和類名一致强胰,或者有繼承關(guān)系舱沧,他們也屬于不懂的類。所以以這種方式加載進(jìn)來的類與原有的類不能互通偶洋,不能污染宿主原有的類熟吏,適合用來動態(tài)加載一些獨立的業(yè)務(wù),比如一些推廣的游戲玄窝,在宿主上提供一個入口牵寺,用戶不需要安裝游戲就能運(yùn)行。因為這種方式起到不用安裝就能部署游戲的作用哆料,所以稱為熱部署缸剪。
插件化
加載外部可執(zhí)行文件的 ClassLoader 實例與宿主的 ClassLoader 實例不是互相獨立的,用宿主的 ClassLoader 加載過的類就無法從外部可執(zhí)行文件中再次加載东亦,它們可以共用一個公共庫杏节,習(xí)慣上把外部可執(zhí)行文件稱為插件。插件里可以存放公共庫里一些借口的實現(xiàn)類典阵,可以有一些新的 Activity 或者 Service 等組件奋渔,可以把一些宿主里的業(yè)務(wù)挪到插件中,插件可以自主升級壮啊,不用隨著宿主 APP 發(fā)版嫉鲸。
熱修復(fù)
在使用插件化技術(shù)的同時,也可以使用插件中的新的類來替換宿主同名的類歹啼,這樣就能修復(fù)宿主中原有的類存在的 BUG玄渗。相比插件化座菠,熱修復(fù)因為不需要考慮組件和 res 資源的問題,所以相對簡單得許多藤树,要保證插件種新的類的加載要在加載宿主中原有類的之前浴滴。
拆分 DEX
相信大家都知道打包 DEX 時 65536 方法數(shù)超標(biāo)問題,也就是一個 DEX 只能有 65536 個方法岁钓,因此有了 multi-dex 的解決方案升略,把本來只有一個的 DEX,拆分成復(fù)數(shù)以上的 DEX屡限,運(yùn)行時挨個加載進(jìn)來品嚣,這其實也算是一種動態(tài)加載,只不過實現(xiàn)過程對開發(fā)者是透明的钧大。
除此之外翰撑,還有另一種拆分 DEX 是用于減少冷啟動的時間的。冷啟動是指應(yīng)用第一次從用戶點擊到完成初始化工作的全部過程啊央。隨著現(xiàn)在 APP 的體積不斷增長额嘿,一些 APP 的 DEX 文件十分龐大,APP 在啟動的時候劣挫,單單加載所有的 DEX 文件就需要非常多的耗時册养,所以用戶點擊 APP 的時候會有一個明顯的卡頓過程。因此有一種拆分 DEX 的方案是 “拆分一個啟動閃屏用的 DEX压固,里面只存放啟動閃屏界面需要用到的類球拦,因此非常小,其他類放到其他 DEX 里面”帐我,啟動的時候因為只需要加載閃屏的 DEX坎炼,所以非常快拦键,APP 進(jìn)入閃屏后谣光,通過異步任務(wù)去完成其他 DEX 的加載,就能消除卡頓的過程芬为。
第一種拆分 DEX 是官方支持的萄金,開發(fā)者只需要打開 multi-dex 功能即可;第二種拆分 DEX 則需要開發(fā)者自己設(shè)計媚朦。
基于 ClassLoader 的動態(tài)加載都有個共同的特點氧敢,就是新的類一旦加載進(jìn)內(nèi)存了,就無法再次替換了询张,所以無法在運(yùn)行時候升級功能孙乖,需要重啟 APP 才能生效。
運(yùn)行在 Native
有另一種動態(tài)加載方式是工作在 Native 層的,相比于 ClassLoader唯袄,在 Native 層的動態(tài)加載不需要重新啟動 APP 就能生效弯屈,這類的加載有 加載 SO 庫 和 基于 JNI HOOK 的熱修復(fù)。
加載 SO 庫
加載 SO 庫是最常見的 Native 動態(tài)加載恋拷,我們項目經(jīng)常中使用 SO 庫季俩,編譯 APP 的時候,SO 并不會參與編譯梅掠,會原封不動被拷貝到 APK 包里的 lib 目錄下,安裝 APK 的時候店归,系統(tǒng)會掃描 lib 文件夾下支持當(dāng)前設(shè)備 CPU 類型(比如 arm 或 x86)的 SO 庫(APK 包會帶有多種 CPU 類型對應(yīng)的 SO 庫阎抒,安裝的時候只需要對應(yīng)類型的)并拷貝到系統(tǒng)安裝目錄,APP 在運(yùn)行時可以調(diào)用 System#loadLibrary
方法動態(tài)加載對應(yīng)的 SO 庫消痛,此外還可以調(diào)用System#load
加載指定路徑上的 SO 庫且叁。
現(xiàn)在的 APK 里面往往帶有非常多的 SO 庫,而 APP 運(yùn)行時只需要用到對應(yīng) CPU 類型的 SO 庫秩伞,因此把 SO 庫從 APK 包里剝離出來也是 APK 瘦身的有效手段逞带。
JNI HOOK
基于 JNI HOOK 的熱修復(fù)技術(shù)的代表框架有阿里巴巴的 AndFix。Android 中纱新,修復(fù) BUG 的方式就是更新類的方法展氓,和 ClassLoader 通過加載新的類來更換方法的實現(xiàn)的想法一樣,AndFix 也是通過更換方法的做法來實現(xiàn)熱修復(fù)脸爱,不過做法比較取巧遇汞。Android 中執(zhí)行 Native 方法的時候,會去 SO 庫中查找對應(yīng)的 C/C++ 方法簿废,而 AndFix 先把普通 Java 方法用 Native 方法代替空入,再通過更換不同 SO 庫還更換 Native 方法的實現(xiàn)。