深入理解Android插件化技術(shù)原理

前言:

插件化技術(shù)最初源于免安裝運行apk的想法,這個免安裝的apk可以理解為插件谤饭。支持插件化的app可以在運行時加載和運行插件,這樣便可以將app中一些不常用的功能模塊做成插件献丑,一方面減小了安裝包的大小排作,另一方面可以實現(xiàn)app功能的動態(tài)擴(kuò)展泡挺。

一渐裸、插件化介紹

1煌抒、插件化介紹

在 Android 系統(tǒng)中仍劈,應(yīng)用是以 Apk 的形式存在的,應(yīng)用都需要安裝才能使用寡壮。

其實 Android 系統(tǒng)在打開應(yīng)用之后贩疙,也只是開始進(jìn)程,然后使用 ClassLoader 加載 classes.dex 至進(jìn)程中况既,執(zhí)行對應(yīng)的組件而已;
那大家可能會想一個問題这溅,既然 Android 本身也是使用類似反射的形式加載代碼執(zhí)行,憑什么我們不能執(zhí)行一個 Apk 中的代碼呢?

這其實就是插件化的目的棒仍,讓 Apk 中的代碼(主要是指 Android 組件)能夠免安裝運行芍躏,這樣能夠帶來很多收益,最顯而易見的優(yōu)勢其實就是通過網(wǎng)絡(luò)熱更新降狠、熱修復(fù);

2、插件化技術(shù)難點

反射并執(zhí)行插件 Apk 中的代碼(ClassLoader Injection)

讓系統(tǒng)能調(diào)用插件 Apk 中的組件(Runtime Container)

正確識別插件 Apk 中的資源(Resource Injection)

3庇楞、雙親委托機制

ClassLoader調(diào)用loadClass方法加載類榜配,代碼如下:

可以看出ClassLoader加載類時,先查看自身是否已經(jīng)加載過該類吕晌,如果沒有加載過會首先讓父加載器去加載蛋褥,如果父加載器無法加載該類時才會調(diào)用自身的findClass方法加載,該機制很大程度上避免了類的重復(fù)加載;

二睛驳、插件化詳解

1烙心、ClassLoader Injection

簡單來說膜廊,插件化場景下,會存在同一進(jìn)程中多個 ClassLoader 的場景:

宿主 ClassLoader:宿主是安裝應(yīng)用淫茵,運行即自動創(chuàng)建

插件 ClassLoader:使用 new DexClassLoader 創(chuàng)建

我們稱這個過程叫做 ClassLoader 注入;

完成注入后爪瓜,所有來自宿主的類使用宿主的 ClassLoader 進(jìn)行加載,所有來自插件 Apk 的類使用插件 ClassLoader 進(jìn)行加載;

而由于 ClassLoader 的雙親委派機制匙瘪,實際上系統(tǒng)類會不受 ClassLoader 的類隔離機制所影響铆铆,這樣宿主 Apk 就可以在宿主進(jìn)程中使用來自于插件的組件類了;

2、Runtime Container

ClassLoader 注入后丹喻,就可以在宿主進(jìn)程中使用插件 Apk 中的類薄货,但是我們都知道 Android 組件都是由系統(tǒng)調(diào)用啟動的,未安裝的 Apk 中的組件碍论,是未注冊到 AMS 和 PMS 的谅猾,就好比你直接使用 startActivity 啟動一個插件 Apk 中的組件,系統(tǒng)會告訴你無法找到;

我們的解決方案很簡單鳍悠,即運行時容器技術(shù)税娜,簡單來說就是在宿主 Apk 中預(yù)埋一些空的 Android 組件,以 Activity 為例贼涩,我預(yù)置一個 ContainerActivity extends Activity 在宿主中巧涧,并且在 AndroidManifest.xml 中注冊它;

它要做的事情很簡單,就是幫助我們作為插件 Activity 的容器遥倦,它從 Intent 接受幾個參數(shù)谤绳,分別是插件的不同信息,如:

pluginName;

pluginApkPath;

pluginActivityName等袒哥,其實最重要的就是 pluginApkPath 和 pluginActivityName缩筛,當(dāng) ContainerActivity 啟動時,我們就加載插件的 ClassLoader堡称、Resource瞎抛,并反射 pluginActivityName 對應(yīng)的 Activity 類;

當(dāng)完成加載后,ContainerActivity 要做兩件事:

1却紧、轉(zhuǎn)發(fā)所有來自系統(tǒng)的生命周期回調(diào)至插件 Activity

2桐臊、接受 Activity 方法的系統(tǒng)調(diào)用,并轉(zhuǎn)發(fā)回系統(tǒng)

我們可以通過復(fù)寫 ContainerActivity 的生命周期方法來完成第一步晓殊,而第二步我們需要定義一個 PluginActivity断凶,然后在編寫插件 Apk 中的 Activity 組件時,不再讓其集成 android.app.Activity巫俺,而是集成自我們的 PluginActivity认烁,后面再通過字節(jié)碼替換來自動化完成這部操作,后面再說為什么,我們先看偽代碼;

但大概原理就是這么簡單却嗡,啟動插件組件需要依賴容器舶沛,容器負(fù)責(zé)加載插件組件并且完成雙向轉(zhuǎn)發(fā),轉(zhuǎn)發(fā)來自系統(tǒng)的生命周期回調(diào)至插件組件窗价,同時轉(zhuǎn)發(fā)來自插件組件的系統(tǒng)調(diào)用至系統(tǒng);

3如庭、Resource Injection

Android 應(yīng)用的開發(fā)其實崇尚的是邏輯與資源分離的理念,所有資源(layout舌镶、values 等)都會被打包到 Apk 中柱彻,然后生成一個對應(yīng)的 R 類,其中包含對所有資源的引用 id;

資源的注入并不容易餐胀,好在 Android 系統(tǒng)給我們留了一條后路哟楷,最重要的是這兩個接口:

1、PackageManager#getPackageArchiveInfo:根據(jù) Apk 路徑解析一個未安裝的 Apk 的 PackageInfo;

2否灾、PackageManager#getResourcesForApplication:根據(jù) ApplicationInfo 創(chuàng)建一個 Resources 實例;

我們要做的就是在上面 ContainerActivity#onCreate 中加載插件 Apk 的時候卖擅,用這兩個方法創(chuàng)建出來一份插件資源實例。具體來說就是先用 PackageManager#getPackageArchiveInfo 拿到插件 Apk 的 PackageInfo墨技,有了 PacakgeInfo 之后我們就可以自己組裝一份 ApplicationInfo惩阶,然后通過 PackageManager#getResourcesForApplication 來創(chuàng)建資源實例,大概代碼像這樣:

拿到資源實例后扣汪,我們需要將宿主的資源和插件資源 Merge 一下断楷,編寫一個新的 Resources 類,用這樣的方式完成自動代理:

然后我們在 ContainerActivity 完成插件組件加載后崭别,創(chuàng)建一份 Merge 資源冬筒,再復(fù)寫 ContainerActivity#getResources,將獲取到的資源替換掉:

這樣就完成了資源的注入

4茅主、解決資源沖突

合并式的資源處理方式舞痰,會引入資源沖突,原因在于不同插件中的資源id可能相同诀姚,所以解決方法就是使得不同的插件資源擁有不同的資源id;

資源id是由8位16進(jìn)制數(shù)表示响牛,表示為0xPPTTNNNN。PP段用來區(qū)分包空間赫段,默認(rèn)只區(qū)分了應(yīng)用資源和系統(tǒng)資源呀打,TT段為資源類型,NNNN段在同一個APK中從0000遞增;

總結(jié)

市面上的插件化框架實際很多糯笙,如 Tecent 的 Shadow贬丛、Didi 的 VirtualApk、360 的 RePlugin炬丸。他們各有各的長處,不過大體上差不多;

他們大體原理其實都差不多,運行時會有一個宿主 Apk 在進(jìn)程中跑稠炬,宿舍 Apk 是真正被安裝的應(yīng)用焕阿,宿主 Apk 可以加載插件 Apk 中的組件和代碼運行,插件 Apk 可以任意熱更新首启。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暮屡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子毅桃,更是在濱河造成了極大的恐慌褒纲,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钥飞,死亡現(xiàn)場離奇詭異莺掠,居然都是意外死亡,警方通過查閱死者的電腦和手機读宙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門彻秆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人结闸,你說我怎么就攤上這事唇兑。” “怎么了桦锄?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵扎附,是天一觀的道長。 經(jīng)常有香客問我结耀,道長留夜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任饼记,我火速辦了婚禮香伴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘具则。我一直安慰自己即纲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布博肋。 她就那樣靜靜地躺著低斋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪匪凡。 梳的紋絲不亂的頭發(fā)上膊畴,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音病游,去河邊找鬼唇跨。 笑死稠通,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的买猖。 我是一名探鬼主播改橘,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼玉控!你這毒婦竟也來了飞主?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤高诺,失蹤者是張志新(化名)和其女友劉穎碌识,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虱而,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡筏餐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了薛窥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胖烛。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诅迷,靈堂內(nèi)的尸體忽然破棺而出佩番,到底是詐尸還是另有隱情,我是刑警寧澤罢杉,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布趟畏,位于F島的核電站,受9級特大地震影響滩租,放射性物質(zhì)發(fā)生泄漏赋秀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一律想、第九天 我趴在偏房一處隱蔽的房頂上張望猎莲。 院中可真熱鬧,春花似錦技即、人聲如沸著洼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽身笤。三九已至,卻和暖如春葵陵,著一層夾襖步出監(jiān)牢的瞬間液荸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工脱篙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留娇钱,地道東北人伤柄。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像文搂,于是被迫代替她去往敵國和親响迂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

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