版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。
目錄(?)[-]
前言項(xiàng)目地址意義DL功能介紹架構(gòu)解析DL對(duì)activity生命周期管理的改進(jìn)DL對(duì)類加載器的支持DL對(duì)宿主host和插件plugin通信的支持DL對(duì)插件獨(dú)立運(yùn)行的支持DLIntent和DLPluginManager開發(fā)規(guī)范轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/singwhatiwanna/article/details/39937639 (來自singwhatiwanna的csdn博客)
好久沒有發(fā)布新的文章,這次打算發(fā)表一下我這幾個(gè)月的一個(gè)核心研究成果:APK動(dòng)態(tài)加載框架(DL)。這段時(shí)間我致力于github的開源貢獻(xiàn),開源了2個(gè)比較有用且有意義的項(xiàng)目,一個(gè)是PinnedHeaderExpandableListView,另一個(gè)是APK動(dòng)態(tài)加載框架。具體可以參見我的github:https://github.com/singwhatiwanna
本次要介紹的是APK動(dòng)態(tài)加載框架(DL),這個(gè)項(xiàng)目除了我以外,還有兩個(gè)共同開發(fā)者:田嘯(時(shí)之沙),宋思宇。
為了更好地理解本文,你需要首先閱讀Android apk動(dòng)態(tài)加載機(jī)制的研究這一系列文章,分別為:
Android apk動(dòng)態(tài)加載機(jī)制的研究
Android apk動(dòng)態(tài)加載機(jī)制的研究(二):資源加載和activity生命周期管理
另外,這個(gè)開源項(xiàng)目我起了個(gè)名字,叫做DL。本文中的DL均指APK動(dòng)態(tài)加載框架。
https://github.com/singwhatiwanna/dynamic-load-apk,歡迎star和fork。
運(yùn)行效果圖:
這里說說這個(gè)開源項(xiàng)目的意義。首先要說的是動(dòng)態(tài)加載技術(shù)(或者說插件化)在技術(shù)驅(qū)動(dòng)型的公司中扮演這相當(dāng)重要的角色,當(dāng)項(xiàng)目越來越龐大的時(shí)候,需要通過插件化來減輕應(yīng)用的內(nèi)存和cpu占用,還可以實(shí)現(xiàn)熱插拔,即在不發(fā)布新版本的情況下更新某些模塊。
我?guī)讉€(gè)月前開始進(jìn)行這項(xiàng)技術(shù)的研究,當(dāng)時(shí)查詢了很多資料,沒有找到很好的開源。目前淘寶、微信等都有成熟的動(dòng)態(tài)加載框架,包括apkplug,但是它們都是不開源的。還有g(shù)ithub上有一個(gè)開源項(xiàng)目AndroidDynamicLoader,其思想是通過Fragment 以及 schema的方式實(shí)習(xí)的,這是一種可行的技術(shù)方案,但是還有限制太多,這意味這你的activity必須通過Fragment去實(shí)現(xiàn),這在activity跳轉(zhuǎn)和靈活性上有一定的不便,在實(shí)際的使用中會(huì)有一些很奇怪的bug不好解決,總之,這還是一種不是特別完備的動(dòng)態(tài)加載技術(shù)。然后,我發(fā)現(xiàn),目前針對(duì)動(dòng)態(tài)加載這一塊成熟的開源基本還是空白的,不管是國(guó)內(nèi)還是國(guó)外。而在公司內(nèi)部,動(dòng)態(tài)加載作為一項(xiàng)核心技術(shù),也不可能是初級(jí)開發(fā)人員所能夠接觸到的,于是,我決定做一個(gè)成熟點(diǎn)的開源,期待能填補(bǔ)這一塊空白。
DL支持很多特性,而這些特性使得插件的開發(fā)過程變得透明、高效。
1. plugin無需安裝即可由宿主調(diào)起。
2. 支持用R訪問plugin資源3. plugin支持Activity和FragmentActivity(未來還將支持其他組件)4. 基本無反射調(diào)用
5. 插件安裝后仍可獨(dú)立運(yùn)行從而便于調(diào)試
6. 支持3種plugin對(duì)host的調(diào)用模式: (1)無調(diào)用(但仍然可以用反射調(diào)用)。 (2)部分調(diào)用,host可公開部分接口供plugin調(diào)用。 這前兩種模式適用于plugin開發(fā)者無法獲得host代碼的情況。(3)完全調(diào)用,plugin可以完全調(diào)用host內(nèi)容。這種模式適用于plugin開發(fā)者能獲得host代碼的情況。
7. 只需引入DL的一個(gè)jar包即可高效開發(fā)插件,DL的工作過程對(duì)開發(fā)者完全透明
8. 支持android2.x版本如果大家閱讀過本文頭部提到的兩篇文章,那么對(duì)DL的架構(gòu)應(yīng)該有大致的了解,本文就不再?gòu)念^開始介紹了,而是從如下變更的幾方面進(jìn)行解析,這些優(yōu)化使得DL的功能和之前比起來更加強(qiáng)大更加易用使用易于擴(kuò)展。
1. DL對(duì)activity生命周期管理的改進(jìn)
2. DL對(duì)類加載器的支持(DLClassLoader)
3. DL對(duì)宿主(host)和插件(plugin)通信的支持
4. DL對(duì)插件獨(dú)立運(yùn)行的支持
5. DL對(duì)activity隨意跳轉(zhuǎn)的支持(DLIntent)
6. DL對(duì)插件管理的支持(DLPluginManager)
其中5和6屬于加強(qiáng)功能,目前正在dev分支上進(jìn)行開發(fā)(本文暫不介紹),其他功能均在穩(wěn)定版分支master上。
大家知道,DL最開始的時(shí)候采用反射去管理activity的生命周期,這樣存在一些不便,比如反射代碼寫起來復(fù)雜,并且過多使用反射有一定的性能開銷。針對(duì)這個(gè)問題,我們采用了接口機(jī)制,將activity的大部分生命周期方法提取出來作為一個(gè)接口(DLPlugin),然后通過代理activity(DLPRoxyActivity)去調(diào)用插件activity實(shí)現(xiàn)的生命周期方法,這樣就完成了插件activity的生命周期管理,并且沒有采用反射,當(dāng)我們想增加一個(gè)新的生命周期方法的時(shí)候,只需要在接口中聲明一下同時(shí)在代理activity中實(shí)現(xiàn)一下即可,下面看一下代碼:
接口DLPlugin
[java] view plain copy public interface DLPlugin { public void onStart(); public void onRestart(); public void onActivityResult(int requestCode, int resultCode, Intent data); public void onResume(); public void onPause(); public void onStop(); public void onDestroy(); public void onCreate(Bundle savedInstanceState); public void setProxy(Activity proxyActivity, String dexPath); public void onSaveInstanceState(Bundle outState); public void onNewIntent(Intent intent); public void onRestoreInstanceState(Bundle savedInstanceState); public boolean onTouchEvent(MotionEvent event); public boolean onKeyUp(int keyCode, KeyEvent event); public void onWindowAttributesChanged(LayoutParams params); public void onWindowFocusChanged(boolean hasFocus); public void onBackPressed(); } 在代理類DLProxyActivity中的實(shí)現(xiàn)[java] view plain copy ... @Override protected void onStart() { mRemoteActivity.onStart(); super.onStart(); } @Override protected void onRestart() { mRemoteActivity.onRestart(); super.onRestart(); } @Override protected void onResume() { mRemoteActivity.onResume(); super.onResume(); } @Override protected void onPause() { mRemoteActivity.onPause(); super.onPause(); } @Override protected void onStop() { mRemoteActivity.onStop(); super.onStop(); } ... 說明:通過上述代碼應(yīng)該不難理解DL對(duì)activity生命周期的管理,其中mRemoteActivity就是DLPlugin的實(shí)現(xiàn)。為了更好地對(duì)多插件進(jìn)行支持,我們提供了一個(gè)DLClassoader類,專門去管理各個(gè)插件的DexClassoader,這樣,同一個(gè)插件就可以采用同一個(gè)ClassLoader去加載類從而避免多個(gè)classloader加載同一個(gè)類時(shí)所引發(fā)的類型轉(zhuǎn)換錯(cuò)誤。
[java] view plain copy public class DLClassLoader extends DexClassLoader { private static final String TAG = "DLClassLoader"; private static final HashMap<String, DLClassLoader> mPluginClassLoaders = new HashMap<String, DLClassLoader>(); protected DLClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, optimizedDirectory, libraryPath, parent); } /** * return a available classloader which belongs to different apk */ public static DLClassLoader getClassLoader(String dexPath, Context context, ClassLoader parentLoader) { DLClassLoader dLClassLoader = mPluginClassLoaders.get(dexPath); if (dLClassLoader != null) return dLClassLoader; File dexOutputDir = context.getDir("dex", Context.MODE_PRIVATE); final String dexOutputPath = dexOutputDir.getAbsolutePath(); dLClassLoader = new DLClassLoader(dexPath, dexOutputPath, null, parentLoader); mPluginClassLoaders.put(dexPath, dLClassLoader); return dLClassLoader; } }這一點(diǎn)很重要,因?yàn)橥拗餍枰筒寮M(jìn)行各種通信,因此DL對(duì)宿主和插件的通信做了很好的支持,目前總共有3中模式,如下圖所示:
下面分別介紹上述三種模式,針對(duì)上述三種模式,我們分別提供了3組例子,其中:
1. depend_on_host:插件完全依賴宿主的模式,適合于能夠能到宿主的源代碼的情況
其中host指宿主工程,plugin指插件工程
2. depend_on_interface:插件部分依賴宿主的模式,或者說插件依賴宿主提供的接口,適合能夠拿到宿主的接口的情況
其中host指宿主工程,plugin指插件工程,common指接口工程
3. main:插件不依賴宿主的模式,這是DL推薦的模式
其中host指宿主工程,plugin指插件工程
模式1:這是DL推薦的模式,對(duì)應(yīng)的工程目錄為main。在這種模式下,宿主和插件不需要通信,兩者是獨(dú)立開發(fā)的,宿主引用DL的jar包(dl-lib.jar),插件也需要引用DL的jar包,但是不能放入到插件工程的libs目錄下面,換句話說,就是插件編譯的時(shí)候依賴jar包但是打包成apk的時(shí)候不要把jar包打進(jìn)去,這是因?yàn)椋琩l-lib.jar已經(jīng)在宿主工程中存在了,如果插件中也有這個(gè)jar包,就會(huì)發(fā)生類鏈接錯(cuò)誤,原因很簡(jiǎn)單,內(nèi)存中有兩份一樣的類,重復(fù)了。至于support-v4也是同樣的道理。對(duì)于eclipse很簡(jiǎn)單,只需要在插件工程中創(chuàng)建一個(gè)目錄,比如external-jars,然后把dl-lib.jar和support-v4.jar放進(jìn)去,同時(shí)在.classpath中追加如下兩句即可:
<classpathentry kind="lib" path="external-jars/dl-lib.jar"/><classpathentry kind="lib" path="external-jars/android-support-v4.jar"/>
這樣,編譯的時(shí)候就能夠正常進(jìn)行,但是打包的時(shí)候,就不會(huì)把上面兩個(gè)jar包打入到插件apk中。
至于ant環(huán)境和gradle,解決辦法不一樣,具體方法后面再補(bǔ)上,但是思想都是一樣的,即:插件apk中不要打入上述2個(gè)jar包。
模式2:插件部分依賴宿主的模式,或者說插件依賴宿主提供的接口,適合能夠拿到宿主的接口的情況。在這種模式下,宿主放出一些接口并實(shí)現(xiàn)一些接口,然后給插件調(diào)用,這樣插件就可以訪問宿主的一些服務(wù)等
模式3:插件完全依賴宿主的模式,適合于能夠能到宿主的源代碼的情況。這種模式一般多用在公司內(nèi)部,插件可以訪問宿主的所有代碼,但是,這樣插件和宿主的耦合比較高,宿主一動(dòng),插件就必須動(dòng),比較麻煩
具體采用哪種方式,需要結(jié)合實(shí)際情況來選擇,一般來說,如果是宿主和插件不是同一個(gè)公司開發(fā),建議選擇模式1和模式2;如果宿主和插件都在同一個(gè)公司開發(fā),那么選擇哪個(gè)都可以。從DL的實(shí)現(xiàn)出發(fā),我們推薦采用模式1,真的需要通信的話采用模式2,盡量不要采用模式3.
為了便于調(diào)試,采用DL所開發(fā)的插件都可以獨(dú)立運(yùn)行,當(dāng)然,這要分情況來說:
對(duì)于模式1,如果插件想獨(dú)立運(yùn)行,只需要把external-jars下的jar包拷貝一份到插件的libs目錄下即可
對(duì)于模式2,只需要提供一個(gè)宿主接口的默認(rèn)實(shí)現(xiàn)即可
對(duì)于模式3,只需要apk打包時(shí)把所引用的宿主代碼打包進(jìn)去即可,具體方式可以參看sample/depend_on_host目錄。
在開發(fā)過程中,應(yīng)該先開啟插件的獨(dú)立運(yùn)行功能以便于調(diào)試,等功能開發(fā)完畢后再將其插件化。
這兩項(xiàng)都屬于加強(qiáng)功能,目前正在dev分支進(jìn)行code review,大家感興趣可以去dev分支上查看,等驗(yàn)證通過即merge到穩(wěn)定版master分支。
DLIntent:通過DLIntent來完成activity的無約束調(diào)起
DLPluginManager:對(duì)宿主的所有插件提供綜合管理功能。
目前DL已經(jīng)達(dá)到了第一個(gè)穩(wěn)定版,經(jīng)過大量機(jī)型的驗(yàn)證,目前得出的結(jié)論是DL是可靠的(兼容到android2.x),可以用在實(shí)際的應(yīng)用開發(fā)中。但是,我們知道,動(dòng)態(tài)加載是一個(gè)技術(shù)壁壘,其很難達(dá)到一種完美的狀態(tài),畢竟,讓一個(gè)apk不安裝跑起來,這是多么不可思議的事情。因此,希望大家辯證地去看這個(gè)問題,下面列出我們目前還不支持的功能,或者說是一種開發(fā)規(guī)范吧,希望大家在開發(fā)過程中去遵守這個(gè)規(guī)范,這樣才能讓插件穩(wěn)定地跑起來。
DL 1.0開發(fā)規(guī)范:
1. 目前不支持service
2. 目前只支持動(dòng)態(tài)注冊(cè)廣播
3. 目前支持Activity和FragmentActivity,這也是常用的activity
4. 目前不支持插件中的assets
5. 調(diào)用Context的時(shí)候,請(qǐng)適當(dāng)使用that,大部分常用api是不需要用that的,但是一些不常用api還是需要用that來訪問。that是apk中activity的基類BaseActivity系列中的一個(gè)成員,它在apk安裝運(yùn)行的時(shí)候指向this,而在未安裝的時(shí)候指向宿主程序中的代理activity,由于that的動(dòng)態(tài)分配特性,通過that去調(diào)用activity的成員方法,在apk安裝以后仍然可以正常運(yùn)行。
6. 慎重使用this,因?yàn)閠his指向的是當(dāng)前對(duì)象,即apk中的activity,但是由于activity已經(jīng)不是常規(guī)意義上的activity,所以this是沒有意義的,但是,當(dāng)this表示的不是Context對(duì)象的時(shí)候除外,比如this表示一個(gè)由activity實(shí)現(xiàn)的接口。
希望能夠給大家?guī)硪恍椭M蠹叶喽嘀С郑?/p>
本開源項(xiàng)目地址:https://github.com/singwhatiwanna/dynamic-load-apk,歡迎大家star和fork。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注