最近要加入一個(gè)新的項(xiàng)目組齿风,使用的技術(shù)都是之前沒(méi)有接觸過(guò)的禾蚕,主要是利用Java的反射您朽,動(dòng)態(tài)加載技術(shù),實(shí)現(xiàn)插件化開(kāi)發(fā)换淆。
目前要解決的問(wèn)題是:實(shí)現(xiàn)框架與app之間的通訊哗总。
所以特別的來(lái)了解一下插件化開(kāi)發(fā)。
1.插件開(kāi)發(fā)的介紹
1. 定義:
? 所謂插件化倍试,就是讓我們的應(yīng)用不必再像原來(lái)一樣把所有的內(nèi)容都放在一個(gè)apk中讯屈,可以把一些功能和邏輯單獨(dú)抽出來(lái)放在插件apk中,然后主apk做到[按需調(diào)用]县习,這樣的好處是一來(lái)可以減少主apk的體積涮母,讓?xiě)?yīng)用更輕便,二來(lái)可以做到熱插拔躁愿,更加動(dòng)態(tài)化叛本。
2. 背景
3. 優(yōu)點(diǎn)
2.插件開(kāi)發(fā)需要解決的問(wèn)題
1.類(lèi)的動(dòng)態(tài)加載
類(lèi)的加載可以使用Java的ClassLoader機(jī)制,但是對(duì)于A(yíng)ndroid來(lái)說(shuō)彤钟,并不是說(shuō)類(lèi)加載進(jìn)來(lái)就可以用了来候,很多組件都是有“生命”的;因此對(duì)于這些有血有肉的類(lèi)逸雹,必須給它們注入活力营搅,也就是所謂的組件生命周期管理;
2. 資源的加載
資源加載方案大家使用的原理都差不多梆砸,都是用AssetManager的隱藏方法addAssetPath剧防。
3.插件開(kāi)發(fā)需要掌握的基礎(chǔ)知識(shí)
- ClassLoader類(lèi)加載器
要想實(shí)現(xiàn)加載外部dex文件(即插件)來(lái)實(shí)現(xiàn)熱部署,那么必然要把其中的class文件加載到內(nèi)存中辫樱。
其中涉及到兩種ClassLoader:DexClassLoader和PathClassLoader。而DexClassLoader可以加載外部的jar,dex等文件俊庇,正是我們需要的狮暑。
關(guān)于ClassLoader詳解鸡挠,見(jiàn)ClassLoader完全解析。
- Java 反射
因?yàn)椴寮pk與宿主apk不在一個(gè)apk內(nèi)搬男,那么一些類(lèi)的訪(fǎng)問(wèn)必然要通過(guò)反射進(jìn)行獲取拣展。所以了解反射對(duì)插件化的學(xué)習(xí)是必須的。
關(guān)于Java反射缔逛,見(jiàn)Java反射詳解备埃。
- 插件資源訪(fǎng)問(wèn)
res里的每一個(gè)資源都會(huì)在R.java里生成一個(gè)對(duì)應(yīng)的Integer類(lèi)型的id,APP啟動(dòng)時(shí)會(huì)先把R.java注冊(cè)到當(dāng)前的上下文環(huán)境褐奴,我們?cè)诖a里以R文件的方式使用資源時(shí)正是通過(guò)使用這些id訪(fǎng)問(wèn)res資源按脚,然而插件的R.java并沒(méi)有注冊(cè)到當(dāng)前的上下文環(huán)境,所以插件的res資源也就無(wú)法通過(guò)id使用了敦冬。
查看源碼辅搬,通過(guò)“addAssetPath”方法重新生成一個(gè)新的Resource對(duì)象來(lái)保存插件中的資源,避免沖突脖旱。
關(guān)于插件資源訪(fǎng)問(wèn)堪遂,見(jiàn)使用插件中的R資源。
- 代理模式
插件化實(shí)現(xiàn)的過(guò)程主要靠欺上瞞下萌庆,坑蒙拐騙來(lái)實(shí)現(xiàn)溶褪。想想雖然加載進(jìn)來(lái)了Activity等組件,但也僅僅是最為一個(gè)對(duì)象而存在践险,并沒(méi)有在A(yíng)ndroidManifest中注冊(cè)猿妈,沒(méi)有生命周期的回調(diào),并不能實(shí)現(xiàn)我們想要的效果捏境。因此無(wú)論是dynamic_load_apk通過(guò)代理activity來(lái)操控插件activity的方式于游,還是DroidPlugin通過(guò)hook activity啟動(dòng)過(guò)程來(lái)啟動(dòng)插件activity的方式,都是對(duì)代理模式的應(yīng)用垫言。
關(guān)于代理模式贰剥,見(jiàn)靜態(tài)代理與動(dòng)態(tài)代理。
至此筷频,通過(guò)ClassLoader加載蚌成,然后通過(guò)代理模式讓Activity等組件具有生命周期實(shí)現(xiàn)真正的功能,并且解決了資源訪(fǎng)問(wèn)問(wèn)題凛捏〉S牵可能插件化已經(jīng)可以簡(jiǎn)單的實(shí)現(xiàn)一些初步的功能,然而插件化絕不止于此坯癣。更多的內(nèi)容仍需要進(jìn)一步探索瓶盛,不過(guò)以上知識(shí)是基礎(chǔ)中的基礎(chǔ),必備之必備。
比較重要的兩個(gè)插件開(kāi)發(fā)框架介紹惩猫,它們的實(shí)現(xiàn)原理不一樣
1. Dynamic_Load_apk (任玉剛)
Dynamic-Load-Apk簡(jiǎn)稱(chēng)DL這個(gè)開(kāi)源框架芝硬,他的實(shí)現(xiàn)方式是,在宿主中埋一個(gè)代理Activity轧房,更改ClassLoader后找到加載插件中的Activity拌阴,使用宿主中的Activity作為代理,回調(diào)給插件中Activity所以對(duì)應(yīng)的生命周期奶镶。
這個(gè)思路與AndroidDynamicLoader有點(diǎn)像迟赃,都是做一個(gè)代理,只不過(guò)Dynamic-load-apk加載的插件中的Activity厂镇。
項(xiàng)目地址:https://github.com/singwhatiwanna/dynamic-load-apk
Dynamic-load-apk詳解
- Android插件化學(xué)習(xí)之路(一)之動(dòng)態(tài)加載綜述
- Android插件化學(xué)習(xí)之路(二)之ClassLoader完全解析
- Android插件化學(xué)習(xí)之路(三)之調(diào)用外部.dex文件中的代碼
- Android插件化學(xué)習(xí)之路(四)之使用插件中的R資源
- Android插件化學(xué)習(xí)之路(五)之代理Activity
- Android插件化學(xué)習(xí)之路(六)之動(dòng)態(tài)創(chuàng)建Activity
- Android插件化學(xué)習(xí)之路(七)之DL插件開(kāi)發(fā)該注意的坑
- Android插件化學(xué)習(xí)之路(八)之DynamicLoadApk 源碼解析(上)
- Android插件化學(xué)習(xí)之路(九)之DynamicLoadApk 源碼解析(下)
2. DroidPlugin (張勇)
DroidPlugin它的原理是Hook客戶(hù)端一側(cè)的系統(tǒng)Api纤壁。
項(xiàng)目地址:https://github.com/DroidPluginTeam/DroidPlugin
DroidPlugin詳解
- Hook機(jī)制之動(dòng)態(tài)代理
- Hook機(jī)制之Binder Hook
- Hook機(jī)制之AMS&PMS
- Activity生命周期管理
- 插件加載機(jī)制
- 廣播的管理
- Service的插件化
- ContentProvider的插件化
4.插件開(kāi)發(fā)的分類(lèi)
插件式編程,由易到難剪撬,分以下個(gè)類(lèi)型來(lái)說(shuō)吧:
- 簡(jiǎn)單的單向接口插件構(gòu)架摄乒。
- 雙向式接口插件構(gòu)架。
- 界面式軟件的插件構(gòu)架残黑。
- 進(jìn)程式插件構(gòu)架
1.單向接口插件構(gòu)架
框架不向插件開(kāi)放任何接口馍佑,框架決定了怎么加載插件,怎么調(diào)用插件梨水,怎么卸載插件拭荤,插件完全是被動(dòng)的,只能干自己的活疫诽,不能反過(guò)來(lái)要求框架舅世。
也就是說(shuō),框架老大奇徒,我說(shuō)怎么干就怎么干雏亚。這種一般用于算法式的框架,比如提供加密算法框架摩钙,主程序不做具體的加密罢低,由插件來(lái)完成加密,每完成一種合適的加密算法胖笛,就加入到插件目錄网持,主程序中就可以多出一種加密算法選擇,具體選擇哪個(gè)长踊,由用戶(hù)來(lái)選擇功舀。
2. 雙向式接口插件構(gòu)架
單向式插件構(gòu)架是最簡(jiǎn)單的一種,插件處于模塊化的地位身弊,沒(méi)有任何的話(huà)語(yǔ)權(quán)辟汰,一般的介紹插件機(jī)制的文章列敲,講的都是這種,比較容易說(shuō)清楚莉擒,但這種情況在真實(shí)項(xiàng)目中酿炸,一般是很少存在的,充其量是個(gè)模塊化編程涨冀,根本算不上插件式構(gòu)架。
框架調(diào)用插件的功能麦萤,天經(jīng)地義鹿鳖,插件反過(guò)來(lái)調(diào)用框架,也是天經(jīng)地義的壮莹。雙方只有交互的情況下翅帜,才能更智能,更符合用戶(hù)習(xí)慣命满,這才能算是一個(gè)標(biāo)準(zhǔn)的插件式構(gòu)架涝滴。
交互就是一個(gè)互相調(diào)用的過(guò)程而已,因此胶台,實(shí)現(xiàn)過(guò)程也不難歼疮,主框架也提供幾個(gè)頭文件,定義好一些接口诈唬,插件在運(yùn)行過(guò)程中韩脏,可以調(diào)用主框架的功能,實(shí)現(xiàn)一些環(huán)境變量的獲取與交互等功能铸磅。在實(shí)際項(xiàng)目中赡矢,插件和框架的頭文件通常都放在一個(gè)目錄即可,雙方各負(fù)責(zé)實(shí)現(xiàn)自己定義好的頭文件阅仔。這點(diǎn)在前面的例子中吹散,已經(jīng)演示過(guò)了,這里說(shuō)說(shuō)運(yùn)行過(guò)程中的加載過(guò)程八酒。
3. 界面式軟件的插件構(gòu)架
這個(gè)分類(lèi)和前面說(shuō)的構(gòu)架空民,是一樣的,唯獨(dú)不一樣的地方在于丘跌,界面袭景。我們現(xiàn)在的客戶(hù)端軟件,如果不提供一個(gè)好看的闭树,易用的界面耸棒,基本上很難擴(kuò)大使用人群,而這點(diǎn)也制約了個(gè)人共享軟件的發(fā)展报辱,現(xiàn)在很難再出現(xiàn)以前那樣有影響力的個(gè)人版本共享軟件了与殃,精力有限。
一個(gè)桌面軟件框架,肯定有一部分的界面幅疼,需要插件來(lái)提供米奸,比如提供工具欄,菜單爽篷,主客戶(hù)端區(qū)域等等悴晰,而主框架也需要開(kāi)放很多接口,供插件操作各種界面元素逐工。這涉及到了大量的界面交互內(nèi)容铡溪,這么一來(lái),整個(gè)框架的復(fù)雜度將會(huì)提高很多泪喊,而框架的接口定義棕硫,也將增加很多,導(dǎo)致整個(gè)接口閱讀和理解的難度增加袒啼。
在界面框架這塊哈扮,事先的預(yù)定義很重要,要根據(jù)項(xiàng)目的內(nèi)容進(jìn)行規(guī)劃蚓再,比如Word這樣的滑肉,一個(gè)或者多個(gè)文檔,各個(gè)插件都可以操作這個(gè)文檔对途,還不需要提供客戶(hù)區(qū)窗口赦邻。還比如winamp那樣的,各個(gè)插件互相不干擾实檀,也不使用主界面惶洲,都提供自己的界面,還比如QQ界面那樣的膳犹,多個(gè)tab頁(yè)恬吕,每個(gè)tab頁(yè)面算一個(gè)插件,點(diǎn)擊不同的tab按鈕须床,出來(lái)自己的界面铐料。這個(gè)界面需求定下來(lái),接口設(shè)計(jì)也就不一樣了豺旬。因此钠惩,構(gòu)架需要按照你的界面要求而開(kāi)發(fā)。
4. 進(jìn)程式界面插件構(gòu)架
最典型的例子族阅,就是早期的某些瀏覽器篓跛,具備多tab頁(yè)面功能,但如果某個(gè)頁(yè)面卡死坦刀,整個(gè)瀏覽器都將無(wú)法使用愧沟,導(dǎo)致其他已經(jīng)打開(kāi)的網(wǎng)頁(yè)也無(wú)法查看了蔬咬。這促使后來(lái)的瀏覽器全部采用了多進(jìn)程方式,也就是說(shuō)沐寺,每個(gè)tab頁(yè)面林艘,實(shí)際上都是一個(gè)新的進(jìn)程,某一個(gè)進(jìn)程卡死混坞,將不會(huì)對(duì)其他進(jìn)程產(chǎn)生影響狐援。
進(jìn)程式插件構(gòu)架實(shí)際上只是把多個(gè)窗口疊加在一起,看起來(lái)像一個(gè)程序拔第,底層則是各走各的路咕村,互不干擾。最基本的原理蚊俺,就是基于WINDOWS的窗口HWND可以互相嵌套的原理。
Windows上面的各個(gè)進(jìn)程逛万,進(jìn)程空間都是互不干擾泳猬,本進(jìn)程要訪(fǎng)問(wèn)其他進(jìn)程的東西,并在它的進(jìn)程中加入自己的代碼宇植,都需要非常麻煩或者說(shuō)高深的技術(shù)得封,但有一樣除外,窗口指郁。只需要使用FindWindow忙上,就可以找到其他進(jìn)程的窗口,窗口時(shí)windows的資源闲坎,所有的進(jìn)程公用這些資源疫粥,當(dāng)窗口所有資源耗盡的時(shí)候,就再也創(chuàng)建不了新的窗口了腰懂。這個(gè)限制梗逮,我得觀(guān)察是6萬(wàn)多個(gè),不知道對(duì)不對(duì)绣溜。自己的窗口在創(chuàng)建的時(shí)候慷彤,需要傳入一個(gè)父窗口句柄,而這個(gè)父窗口句柄怖喻,是沒(méi)有進(jìn)程限制的底哗。
進(jìn)程式插件在解決了窗口問(wèn)題之后,另外面臨的問(wèn)題锚沸,就是通訊問(wèn)題跋选,原來(lái)的接口指針,直接調(diào)用即可咒吐,現(xiàn)在這些指針野建,全部不能使用了属划。這里,我們可以采用如下圖所示方案候生。
- 主框架接口
- 通訊模塊
- 加封/解封裝模塊
- 插件模塊接口
- 加封/解封模塊
- 通訊模塊
如果把上面的通訊模塊同眯,換成http通訊,把加封/解封換成xml封裝方式唯鸭,是不是和“soap”協(xié)議一模一樣了须蜗?沒(méi)錯(cuò),標(biāo)準(zhǔn)的SOAP協(xié)議就是這么做的目溉。我們可以不用HTTP明肮,簡(jiǎn)單的TCP或者管道通訊即可,把接口用xml方式封裝缭付,然后傳遞到對(duì)端柿估,即可形成插件調(diào)用機(jī)制。
作為構(gòu)架陷猫,主要是把接口開(kāi)放給插件秫舌,但要屏蔽掉進(jìn)程通訊相關(guān)的細(xì)節(jié),因此把以上的通訊機(jī)制绣檬,封裝成一個(gè)lib或者dll足陨,在插件中,調(diào)用這些函數(shù)的時(shí)候娇未,通過(guò)通訊墨缘,會(huì)調(diào)用到主框架程序中。只要這個(gè)封裝庫(kù)做的夠好零抬,插件幾乎完全不知道自己是進(jìn)程式插件镊讼,還是單進(jìn)程式插件。