Android-Plugin-FrameworkAndroid 插件框架
此項(xiàng)目是Android插件框架完整源碼以及實(shí)例。用來(lái)開發(fā)Android插件APK,并通過動(dòng)態(tài)加載的方式在宿主程序中運(yùn)行。
若插件APK是完全獨(dú)立的APK,那么插件apk也可獨(dú)立安裝運(yùn)行。 若插件APK不是完全獨(dú)立的apk,比如和插件宿主程序共用一些依賴庫(kù),那么插件apk只能在宿主程序中運(yùn)行。不可獨(dú)立運(yùn)行。 因?yàn)榇藭r(shí)插件apk的代碼是不完整的。
目錄結(jié)構(gòu)說明:
PluginCore工程是插件庫(kù)核心工程。用于提供對(duì)插件功能的支持。
PluginMain是用來(lái)測(cè)試的插件宿主程序Demo工程。
PluginShareLib是用來(lái)測(cè)試的插件宿主程序的依賴庫(kù)Demo工程
PluginTest是用來(lái)測(cè)試的插件Demo工程。此工程下有用來(lái)編譯插件的ant腳本。
宿主程序工程可以通過ant編譯或者導(dǎo)入eclipse后直接點(diǎn)擊Run菜單進(jìn)行安裝。
插件Demo工程需要通過插件ant腳本編譯。編譯命令為 “ant clean debug” 原因是Demo中引用了宿主程序的依賴庫(kù)。需要在編譯時(shí)對(duì)共享庫(kù)進(jìn)行排除。 插件編譯出來(lái)以后,可以將插件復(fù)制到sdcard,然后在宿主程序中調(diào)用PluginLoader.installPlugin("插件apk絕對(duì)路徑")進(jìn)行安裝
還有一種簡(jiǎn)易的安裝方式,是使用編譯命令為 “ant clean debug install” 直接將插件apk安裝到系統(tǒng)中,PluginMain工程會(huì)監(jiān)聽系統(tǒng)的應(yīng)用安裝廣播,監(jiān)聽到插件apk安裝廣播后, 再自動(dòng)調(diào)用PluginLoader.installPlugin("/data/app/插件apk文件.apk")進(jìn)行插件安裝。免去復(fù)制到sdcard的過程。
(雖然沒有用過apkplug、以及另外一個(gè)插件框架作者singwhatiwanna寫的DL框架,但是看過他們的一些介紹文檔,感覺自己的這份實(shí)現(xiàn)應(yīng)該是更簡(jiǎn)單易用更完善。。。哈哈,是不是有王婆賣瓜的嫌疑。)
已支持的功能:
1、插件apk無(wú)需安裝,由宿主程序動(dòng)態(tài)加載運(yùn)行。
2、插件形式支持fragment和activity代理。 這兩種形式是插件開發(fā)中的兩種主要形式。
3、插件支持activity非代理模式,真正實(shí)現(xiàn)Activity無(wú)需注冊(cè),既不用反射,也不用代理,實(shí)現(xiàn)Activity完整生命周期。
4、插件庫(kù)apk可無(wú)任何特殊代碼。如插件中的fragment,activity等無(wú)需繼承任何特定基類或接口。完全和普通app代碼沒有區(qū)別.
5、支持插件共用宿主程序依賴庫(kù)提供的自定義控件
6、支持插件中使用自定義控件
7、支持插件資源和宿主資源混合使用。意即支持如下場(chǎng)景:
插件程序和宿主程序共用依賴庫(kù)時(shí)插件中的布局文件中使用了宿主程序中的自定義控件,而宿主程序中的自定義控件中又使用 了宿主程序中的布局文件。 插件代碼中無(wú)需做任何特殊處理,即可支持這種布局文件。
8、插件中的各種資源、布局、R、以及宿主程序中的各種資源、布局、R等可隨意使用、也無(wú)任何特殊代碼。
10、支持插件使用宿主程序主題和系統(tǒng)主題
11、解決資源id沖突問題。嘗試做過插件開發(fā)的同學(xué)應(yīng)該都遇到,插件資源id和宿主程序資源id可能相同,導(dǎo)致獲取的資源不是想要的資源。 此問題其實(shí)在android提供的編譯器aapt中早已提供了支持。
12、需要關(guān)注PluginTest工程的ant.properties文件和project.properties文件以及custom_rules.xml文件,插件使用宿主程序共享庫(kù),以及共享庫(kù)R引用,和編譯時(shí)排除的功能,都在這3個(gè)配置文件中體現(xiàn)
暫不支持的功能:
1、暫不支持使用插件中自定義主題,
2、支持在插件中通過R文件使用宿主程序中的資源,暫不支持插件資源文件中直接使用宿主程序中的資源。但是支持間接使用。 例如在上述“已支持的功能”6中描述的,實(shí)際就是間接使用。
后續(xù)需要解決的問題:
1、使支持插件自定義主題
2、使插件中對(duì)activity的支持更穩(wěn)定。
由于此框架沒有實(shí)際的項(xiàng)目應(yīng)用,目前對(duì)activity的提供標(biāo)準(zhǔn)API的測(cè)試還不夠完全,可能在其他開發(fā)場(chǎng)景中,activity的部分標(biāo)準(zhǔn)API可能會(huì)出現(xiàn)問題。畢竟這里使用了很多反射,會(huì)涉及到多機(jī)型多系統(tǒng)版本的兼容問題。后續(xù)還需要持續(xù)測(cè)試和完善 上述第2點(diǎn)問題已解決、請(qǐng)看已支持的功能第3條。
3、使支持插件資源文件中直接引用依賴庫(kù)中的資源。目測(cè)可能需要重寫android自帶的aapt程序。
實(shí)現(xiàn)原理簡(jiǎn)介:
1、插件apk的class
通過構(gòu)造插件apk的Dexclassloader來(lái)加載插件apk中的類。DexClassLoader的parent設(shè)置為宿主程序的classloader,即可將主程序和插件程序的class貫通
2、插件apk的資源
通過構(gòu)造插件apk的AssetManager和Resouce類即可。 本項(xiàng)目最關(guān)鍵一點(diǎn)功能、也是和網(wǎng)上其他插件項(xiàng)目不同的地方之一,在于 通過addAssetsPath方法添加資源的時(shí)候,同時(shí)添加了插件程序的資源文件和宿主程序的資源。這樣就 可以做到插件資源合并。很多資源文件都迎刃而解。
3、插件apk中的資源id
完成上述第二點(diǎn)以后,還有需要解決的難題,是宿主程序資源id和插件程序id重復(fù)的問題。 這個(gè)問題解決辦法也很簡(jiǎn)單 我們知道,資源id是在編譯時(shí)生成的,其生成的規(guī)則是0xPPTTNNNN PP段,是用來(lái)標(biāo)記apk的,默認(rèn)情況下系統(tǒng)資源PP是01,應(yīng)用程序的PP是07 TT段,是用來(lái)標(biāo)記資源類型的,比如圖標(biāo)、布局等,相同的類型TT值相同,但是同一個(gè)TT值不代表同一種資源,例如這次編譯的時(shí)候可能使用03作為layout的TT,那下次編譯的時(shí)候可能會(huì)使用06作為TT的值,具體使用那個(gè)值,實(shí)際上和當(dāng)前APP使用的資源類型的個(gè)數(shù)是相關(guān)聯(lián)的。 NNNN則是某種資源類型的資源id,默認(rèn)從1開始,依次累加。 那么我們要解決資源id問題,就可從TT的值開始入手,只要將每次編譯時(shí)的TT值固定,即可是資源id達(dá)到分組的效果,從而避免重復(fù)。 例如將宿主程序的layout資源的TT固定為03,將插件程序資源的layout的TT值固定為23,即可解決資源id重復(fù)的問題了。 固定資源idTT值的版本也非常簡(jiǎn)單,提供一份public.xml,在public.xml中指定什么資源類型以什么TT值開頭即可。 具體public.xml如何編寫,可參考PluginMain/res/values/public.xml 以及 PluginTest/res/values/public.xml倆個(gè)文件,它們是分別用來(lái)固定宿主程序和插件程序資源id的范圍的。
4、插件apk的Context
構(gòu)造一個(gè)Context即可,具體的Context實(shí)現(xiàn)請(qǐng)參考PluginCore/src/com/plugin/core/PluginContextTheme.java 關(guān)鍵是要重寫幾個(gè)獲取資源、主題的方法,以及重寫getClassLoader方法
5、插件中的LayoutInfalter
通過第4步構(gòu)造出來(lái)的Context獲取LayoutInfater即可
6、如何實(shí)現(xiàn)插件代碼不依賴任何特殊代碼,如繼承特定的基類、接口等。
要做到這一點(diǎn),最主要的是實(shí)現(xiàn)上述第4步,構(gòu)造出插件的Context后,所有問題都可以得到解決。
7、插件中Activity無(wú)需注冊(cè),擁有完整生命周期是如何實(shí)現(xiàn)的。
眾所周知、Activity是系統(tǒng)組件,必須在manifest中注冊(cè)才能被系統(tǒng)喚起并擁有完整生命周期,通過Activity代理和放射的方式實(shí)現(xiàn)的 實(shí)際是偽生命周期。并非完整生命周期。那么如果才能實(shí)現(xiàn)activity免注冊(cè),而且擁有完整的生命周期呢,這要從activity的啟動(dòng)流程中 入手。 App安裝時(shí),系統(tǒng)會(huì)掃描app的Manifest并緩存到一個(gè)xml中,activity啟動(dòng)時(shí),系統(tǒng)會(huì)現(xiàn)在查找緩存的xml,如果查到了,再通過classLoad去load這個(gè)class,并構(gòu)造一個(gè)activity實(shí)例。那么我們只需要將classload加載這個(gè)class的時(shí)候做一個(gè)簡(jiǎn)單的映射,讓系統(tǒng)以為加載的是A class,而實(shí)際上加載的是B class,達(dá)到掛羊頭買狗肉的效果,即可將預(yù)注冊(cè)的Aclass替換為未注冊(cè)的activity,從而實(shí)現(xiàn)插件中的Activity 完全被系統(tǒng)接管,而擁有完整生命周期。 在PluginMain和PluginTest已經(jīng)添加了這種實(shí)現(xiàn)方式的測(cè)試實(shí)例。
8、通過activity代理方式實(shí)現(xiàn)加載插件中的activity是如何實(shí)現(xiàn)的
要實(shí)現(xiàn)這一點(diǎn),同樣是基于上述第4點(diǎn),構(gòu)造出插件的Context后,通過attachBaseContext的方式,替換代理Activiyt的context即可。 另外還需要在獲得插件Activity對(duì)象后,通過反射給Activity的attach()方法中attach的成員變量賦值。 這樣可解決另外一個(gè)插件框架作者singwhatiwanna實(shí)現(xiàn)的代碼中所謂this和that的問題。也是可以使插件Activity不需要繼承任何特定基類或者接口的原因。
9、activity代理實(shí)現(xiàn)后,其他組件,如service等,如法炮制即可。
10、插件編譯問題。
如果插件和宿主共享依賴庫(kù),那邊編譯插件的時(shí)候不可將共享庫(kù)編譯到插件當(dāng)中,包括共享庫(kù)的代碼以及R文件,但是需要在編譯時(shí)添加到classpath中,且插件中如果要使用共享依賴庫(kù)中的資源,需要使用共享庫(kù)的R文件來(lái)進(jìn)行引用。這幾點(diǎn)在PluginTest示例工程中有體現(xiàn)。
11、插件開發(fā)模式 插件UI可通過fragment或者activity來(lái)實(shí)現(xiàn) 如果是activity實(shí)現(xiàn)的插件,則最終會(huì)在PluginProxyActivity中運(yùn)行
如果是fragment實(shí)現(xiàn)的插件,又分為兩種 1種是fragment運(yùn)行在任意支持fragment的activity中,這種方式,在開發(fā)fragment的時(shí)候,fragmeng中凡是要使用context的地方,都需要使用通過PluginLoader.getPluginContext()獲取的context,那么這種fragment對(duì)其運(yùn)行容器沒有特殊要求 還有1種是,fragment運(yùn)行在PluginCore提供的PluginSpecDisplayer中,這種方式,由于其運(yùn)行容器PluginSpecDisplayer的Context已經(jīng)被PluginLoader.getPluginContext獲取的context替換,因此這種fragment的代碼和普通非插件開發(fā)時(shí)開發(fā)的fragment的代碼沒有任何區(qū)別。
需要注意的問題
項(xiàng)目插件開發(fā)后,特別需要注意的是宿主程序混淆問題。宿主程序混淆后,可能會(huì)導(dǎo)致插件程序運(yùn)行時(shí)出現(xiàn)classnotfound
