ZeusPlugin插件框架項目
本項目為ZeusPlugin插件框架項目,ZeusPlugin為插件框架代碼,app為測試插件與補(bǔ)丁的項目demo,testplugin為插件demo, testhotfix為補(bǔ)丁demo。絕大部分核心代碼都在PluginManger.java中。PluginManager也是入口類,核心方法是inite初始化、loadLastVersionPlugin加載插件、reloadInstalledPluginResources加載插件與補(bǔ)丁的資源、loadHotfixPluginClassLoader加載補(bǔ)丁的類。插件與補(bǔ)丁更新的最小單位是java類(不局限四大組件)。
插件的定位
提高某些功能的升級率,使功能可以不通過安裝新apk版本進(jìn)行更新,可以實現(xiàn)wifi/移動環(huán)境下用戶無感知的更新功能。如果電商類的網(wǎng)頁,可以通過url告知客戶端使用哪個插件并可以指定最低版本,然后客戶端發(fā)現(xiàn)存在符合的插件就加載使用。如:http//www.baidu.com/a.php?p=zeusplugin_test&pversion=2,表示該頁面使用zeusplugin_test插件,插件的最低版本號為2。宿主發(fā)現(xiàn)存在符合的插件則加載并將該url傳給插件,不存在則進(jìn)入下載流程,下載完成后即可加載。插件的下發(fā)可以通過push或者輪訓(xùn)的方式預(yù)下載增量更新包,或者進(jìn)入loading頁面同步下載,下載完成后即可加載使用。這時插件就可以是Fragment、View、Activity等等。
補(bǔ)丁使用的定位
解決某個版本發(fā)布后的bug,或者是更新某個功能。由于代碼會進(jìn)行混淆,一個補(bǔ)丁通常只能對應(yīng)一個版本。
插件補(bǔ)丁框架的核心思想
替換系統(tǒng)使用的
ClassLoader,通過閱讀源碼發(fā)現(xiàn)系統(tǒng)反射四大組件都是用的ContextImpl中的mPackageInfo中的mClassLoader成員變量。所以為了能讓系統(tǒng)生成四大組件,我們通過反射修改了mClassLoader成員變量。補(bǔ)丁原理則是ClassLoader優(yōu)先查找補(bǔ)丁中的類,如果存在則返回,然后再查找原宿主中的類,我們是通過反射的方式設(shè)置宿主ClassLoader的parent成員來完成的。替換系統(tǒng)獲取資源用到的
Resources對象并使該對象可以訪問到所有插件和補(bǔ)丁的資源,Resources是通過AssetManager來訪問資源的。系統(tǒng)的Resources對象是ContextImpl的mPackageInfo中的mResources成員。因此我們生成了一個PluginResources對象并創(chuàng)建一個可以訪問所有插件的AssetManager,反射調(diào)用addAssetPath將插件/補(bǔ)丁的路徑都添加上。 通過反射修改了mResources成員變量。但是部分手機(jī)是通過獲取調(diào)用Activity/Application中的getResources來訪問,我們重寫該方法,并返回生成的PluginResources。
以上就是我們的核心,修改生成類和資源的成員變量。
支持運(yùn)行時更新的原理
插件更新后,插件在訪問資源應(yīng)該都是新插件中的資源。因此我們重新創(chuàng)建一個
Resources,這個新的Resources只能訪問宿主跟新插件中的資源。這解決了資源更新的問題。插件更新后,插件在生成類的時候應(yīng)該生成新插件中的類。因此我們重新創(chuàng)建了一個插件ClassLoader來替換原來的
ClassLoader,在方案中,一個插件對應(yīng)一個ClassLoader,ZeusClassLoader是個空殼,它內(nèi)部有一個數(shù)組保存了所有的插件ClassLoader,zeusClassLoader首先查找原宿主apk的ClassLoader,因為宿主apk的ClassLoader的parent為補(bǔ)丁ClassLoader,所以先在補(bǔ)丁中查找類,然后在宿主apk中查找,最后依次在插件中查找。我們只要替換了插件ClassLoader,那么新的插件ClassLoader自然只能訪問新插件中的類了。系統(tǒng)在解析
xml生成View的時候都是通過反射來生成View的,系統(tǒng)為了加快速度會把所有已經(jīng)反射過的View的構(gòu)造函數(shù)都保存在LayoutInflater的靜態(tài)成員變量sConstructorMap中,所以更新了插件后,我們會清除該map中的所有對象。在使用舊版本插件時,可以安裝新版本插件,每個插件的安裝地址都是隨機(jī)的,有個
pathInfo文件來保存最新插件的隨機(jī)安裝路徑。一旦加載最新插件時,ClassLoader和AssetManager都指向最新版本的插件,當(dāng)然so路徑也是隨機(jī)的。當(dāng)軟件退出或者再次啟動的時候會清理掉老版本插件,因為pathInfo只保留最新插件的安裝地址,這些老版本插件就已經(jīng)不可訪問了,只能加載最新版本的插件。
使用步驟
使原應(yīng)用程序的
Application繼承BaseApplication。或者將BaseApplication代碼拷貝至自己的Application中。具體參考app中的MyApplication使原應(yīng)用程序的Activity都繼承
BaseActivity?;蛘邔?code>BaseActivity代碼拷貝至自己的Activity中。具體參考app中的MainActivity內(nèi)置的插件應(yīng)放入assets目錄中。插件的命名以
PluginConfig.EXP_PLUG_PREFIX為前綴,以PluginConfig.PLUGINWEB_APK_SUFF為結(jié)尾。內(nèi)置的插件必須在插件項目的assets中添加
PluginConfig.PLUGINWEB_MAINIFEST_FILE(即plugin.meta)文件,該文件為插件的配置文件。配置如插件名稱(name),插件版本(version),插件支持的宿主最低版本(minVersion),插件的入口類名(mainClass,可不寫)。具體參考testplugin例子。PluginConfig中是一些可以配置的信息,建議除了內(nèi)置插件目錄以外都不要修改。請將當(dāng)前aapt目錄下
aapt.exe拷貝到sdk目錄下的build-tools中的正在使用的打包工具,替換原有的aapt.exe, 該aapt.exe是基于6.0源碼編譯,包含windows、mac和linux(64位)版本。測試替換23.0.2、23.0.3都沒有問題。該aapt.exe還集成了資源混淆功能。需要在build.gradle中進(jìn)行配置。 如下:aaptOptions.additionalParameters '--PLUG-resoure-proguard', '--PLUG-resoure-id', '0x7d''--PLUG-resoure-proguard'表明開啟資源混淆,可以不寫,不寫表示不開啟資源混淆,'--PLUG-resoure-id'表示設(shè)置資源的packageID,'0x7d'表示資源packageID為0x7d開頭。插件或補(bǔ)丁的資源
packageID不能與其他插件或者是宿主相同。具體如何設(shè)置請參考testplugin中的build.gradle。將
app模塊中的buil.gradle中的buildJar方法拷貝到你的build.gradle中,這個方法是用來生成插件的sdk,生成sdk的jar文件在build/libs路徑下, 把生成的jar放到插件項目的sdk-jars,要把什么類放到sdk中,請對該方法進(jìn)行修改。具體參考testplugin。請將插件的
AndroidManifest.xml中有關(guān)的配置添加到宿主中,包括四大組件、權(quán)限申請、meta-data等,為了插件的擴(kuò)展也可以在宿主中預(yù)定義一些組件。
以下是補(bǔ)丁相關(guān)的。補(bǔ)丁與插件類似,只不過補(bǔ)丁把實時加載的功能去掉了。如果項目只運(yùn)行在android 4.4及以上(art虛擬機(jī),部分低于4.4的手機(jī)也可以去掉),則1忽略,以上就已經(jīng)支持補(bǔ)丁了,可以直接運(yùn)行app模塊,不需要以下的額外操作。
如果要支持bug fix的補(bǔ)丁功能,請把gradleplugin拷貝到工程里,并將project的
build.gradle中的帶有“//-----補(bǔ)丁相關(guān)-------”的相關(guān)配置移植到你的項目里。如果你的項目只支持android 4.4及以上時(比如內(nèi)置應(yīng)用),不需要按照第當(dāng)前移植即可支持bug fix補(bǔ)丁功能。具體請查看testhotfix模塊。bug fix的補(bǔ)丁需要以
PluginConfig.EXP_PLUG_HOT_FIX_PREFIX為開頭,補(bǔ)丁apk中res文件夾中必須有實際的資源,實在沒有就隨便寫個com.android.internal.util.Predicate要保留,故意讓所有的類中都包含這個類,這個類系統(tǒng)也定義了,所以dalvik虛擬機(jī)生成dex的時候會給當(dāng)前記一個標(biāo)記,這樣補(bǔ)丁才能實現(xiàn)。插件與補(bǔ)丁都需要先安裝,插件可以任意時刻進(jìn)行加載,補(bǔ)丁則下次啟動由框架加載,不提供實時加載,實時加載之后你的內(nèi)存中的對象可能就會亂套了。插件與補(bǔ)丁的安裝代碼如下:
PluginManager.getPlugin(pluginName).install();如果宿主被混淆請打補(bǔ)丁包時使用-applymapping,具體請搜索,在此不做詳細(xì)介紹。
補(bǔ)丁只在宿主的release包才生效,請在release包中進(jìn)行測試。
補(bǔ)丁在
android studio中開啟instant run時調(diào)試是無效的,如果要生效就得更改代碼,具體如何更改請查看PluginManager.java。
支持特性
支持插件的安裝、升級、卸載、版本管理
支持插件調(diào)用宿主的類與資源。要在插件中使用宿主的資源ID,需要使用public.xml將資源ID固定,public.xml如何使用請自行搜索,并將該ID添加到sdk-jar中,如果只是插件調(diào)用宿主中的某個類,然后這個使用了宿主資源則不需處理。
支持插件的動態(tài)實時升級。只需要調(diào)用
PluginManager.loadLastVersionPlugin(pluginName)即可使用最新版本的插件。插件與宿主的關(guān)系和apk與android系統(tǒng)的關(guān)系接近。如果插件中有與宿主重名的類,這個插件中的類只能被插件使用,宿主是不會使用插件中的類的。宿主只能通過顯式loadClass的方式才能訪問插件。
當(dāng)插件版本過多又怕新插件在早期apk中不支持,應(yīng)編寫一個類CTS測試(google強(qiáng)制廠商執(zhí)行的兼容性測試)的小插件,該插件中會調(diào)用所有之前插件用到的宿主中的所有方法和成員等等。如果該小程序跑過了則說明新版本apk兼容所有插件。
支持so以及so的動態(tài)實時升級。
插件與補(bǔ)丁支持加固方案,單dex或者多個dex文件情況,已對android 1.5以上版本(已適配最新的android N)和廠商定制的android系統(tǒng)進(jìn)行了適配,適配了各種機(jī)型和廠商自己的系統(tǒng)(包括yunOS等)。測試無資源加載找不到的問題,存在極個別的第一次 加載后類找不到的情況,嘗試幾次就可以了。(概率極低,<0.0001%)
對性能無明顯影響。經(jīng)過在android 2.2及以上進(jìn)行高強(qiáng)度測試,對性能無明顯影響。
支持bug fix的補(bǔ)丁功能,補(bǔ)丁修復(fù)最小單位是java中的class,補(bǔ)丁中可以有資源,也可以使用宿主的資源,它其實跟插件是一樣的,只不過補(bǔ)丁的class 與宿主的class重名了,發(fā)現(xiàn)重名就替換,支持單dex、多dex(方法數(shù)超了的情況)。補(bǔ)丁對性能有微弱影響(個人認(rèn)為可以忽略),android 4.4及以上完全無影響。
如果你的apk沒有進(jìn)行代碼混淆,補(bǔ)丁也可以產(chǎn)生與插件相同的作用來進(jìn)行功能的更新。
不支持的特性
不支持插件中使用activity動畫。如果要使用activity動畫請將activity動畫用到的xml文件放到宿主中,否則卡死。
不支持插件有自己的Application,插件獲取的是宿主的application。
不支持動態(tài)升級插件的AndroidManifest.xml文件,所有試圖修改AndroidManifest.xml的功能都需要升級宿主。不過這種情況很少,目前我們還沒遇到過。
不支持補(bǔ)丁實時加載,下次啟動才能加載,否則內(nèi)存中的對象會亂掉,如之前保存了A類的實例,現(xiàn)在A類已經(jīng)被實時替換為B類了,那么之前的A類實例就不能轉(zhuǎn)為B類了。
不支持插件在xml使用宿主的自定義屬性。(支持這個性價比太低,請使用其他替代方法)
其他還不清楚,還請大家進(jìn)行測試。
