之前已經(jīng)對(duì)我們學(xué)習(xí)插件化原理需要的預(yù)備知識(shí)進(jìn)行了比較詳細(xì)的講解了,從這篇文章開(kāi)始,我們將具體介紹插件化原理,同時(shí)會(huì)根據(jù)原理寫(xiě)一個(gè)比較簡(jiǎn)單的插件化管理器。
插件化主要用到的技術(shù)知識(shí)有:
- Android ClassLoader 加載 class 文件原理,這也是插件化最重要的技術(shù)點(diǎn),我們?cè)谏掀恼轮兄v解的也比較詳細(xì)了,插件化框架都會(huì)通過(guò)自定義 ClassLoader 來(lái)加載插件中的 class 文件。
- Java 反射原理,這是制作插件化框架中最基礎(chǔ)和最核心的知識(shí)點(diǎn)了。
- Android 資源加載原理,即 Android 如何加載資源文件,主要通過(guò) Resource 類(lèi)和 AssetManager 類(lèi)等來(lái)完成資源的加載。
- 四大組件加載原理。了解四大組件的加載流程,以及它們是如何通過(guò) ActivityManagerService 完成與系統(tǒng)的通信。
這四點(diǎn)是最基本要了解的點(diǎn),你還需要了解像 Gradle 打包原理,Android Framework 層以及一些包的管理——PackageManager 的原理,所以如果想制作一個(gè)插件化框架,實(shí)際上是非常復(fù)雜的,要對(duì) Android 系統(tǒng)非常熟悉,這里我們只講解基本原理。
Manifest 處理
清單文件在 App 中非常重要,它記錄了你的應(yīng)用中有哪些組件。

這是市面上大部分的框架對(duì)清單文件的處理方式。首先,無(wú)論是宿主 app 還是 aar 還是 Bundle,都是有自己的清單文件的,那么我們平時(shí)使用的時(shí)候,當(dāng)我們依賴(lài) library 或者 aar 的時(shí)候,就會(huì)有多個(gè)清單文件,這個(gè)時(shí)候,Gradle 在構(gòu)建 app 產(chǎn)物的時(shí)候,會(huì)將 aar 的 manifest 文件 merge 到 app module 中的清單文件中。
基于這個(gè)原理,插件化框架會(huì)修改整個(gè)打包流程,在輸出 apk 的 manifest 文件的時(shí)候,會(huì)將所有插件的清單文件都合并到宿主清單文件中,這樣的話(huà),宿主 manifest 就記錄了所有插件和 aar 文件中所有清單的內(nèi)容,這樣就可以保證我們調(diào)用各個(gè)插件組件的時(shí)候不會(huì)報(bào)錯(cuò)。
光是清單文件的處理,就比較復(fù)雜了。你不僅要了解 Gradle 的打包原理,甚至還需要去修改流程。這樣才能打到合并清單文件的要求。
我們來(lái)總結(jié)下插件化框架對(duì)于 manifest 的處理,主要工作主要有兩個(gè):
- 文件的合并,實(shí)際上就是一個(gè) IO 操作,將所有的清單文件合并到一個(gè)總的 manifest 文件中。
- 修改構(gòu)建流程,在構(gòu)建時(shí)將所有插件的清單文件合并到宿主的 manifest 文件中。大家有興趣的話(huà)可以深入了解下如何修改編譯流程從而完成清單文件的合并。
插件類(lèi)加載

每個(gè)插件實(shí)際上都是一個(gè) APK,每個(gè) APK 有自己的 Dex 文件,所有的 class 字節(jié)碼就都存儲(chǔ)在了這些 dex 文件中的。市面上絕大多數(shù)的插件化框架都是根據(jù)上圖的這樣的方式來(lái)加載插件中的類(lèi)的。
在加載之前,他首先會(huì)區(qū)分宿主 apk 和 插件 apk,這樣區(qū)分的好處是,因?yàn)樗拗?apk 已經(jīng)安裝到了系統(tǒng)中了,所以系統(tǒng)會(huì)給宿主 apk 創(chuàng)建 ClassLoader ,而無(wú)需手動(dòng)去創(chuàng)建了。所以宿主 apk 的 ClassLoader 使用 PathClassLoader 就完全夠用了,PathClassLoader 可以加載已安裝 apk 中的類(lèi),這個(gè)我們?cè)谥暗奈恼轮幸呀?jīng)分析過(guò)了。
對(duì)于插件 apk 來(lái)說(shuō),因?yàn)闆](méi)有安裝到我們系統(tǒng)中,所以插件 apk 本身是沒(méi)有 ClassLoader 的,系統(tǒng)也不會(huì)幫我們創(chuàng)建,需要我們自己手動(dòng)創(chuàng)建 apk,插件化框架會(huì)給每個(gè)插件創(chuàng)建對(duì)應(yīng)的 ClassLoader,在加載插件中 apk 文件的時(shí)候,就使用我們創(chuàng)建的 ClassLoader 來(lái)加載插件 apk 中的類(lèi)。
根據(jù)這個(gè)思路,我們就會(huì)引出兩個(gè)問(wèn)題:
- 如何自定義 ClassLoader 加載類(lèi)文件
- 如何調(diào)用插件 apk 文件中的類(lèi)
下面我們就簡(jiǎn)單實(shí)現(xiàn)下上面兩個(gè)問(wèn)題的代碼,通過(guò)簡(jiǎn)單的模擬來(lái)理解原理。
首先我們新建一個(gè)項(xiàng)目,并且在項(xiàng)目下再新建一個(gè) Project 作為插件模塊。

這里的app代表宿主模塊,app.bundle代表某個(gè)插件模塊,名字大家不要過(guò)多糾結(jié),這里只是為了遵循 Small 框架而起的插件名。app.bundle中有一個(gè)簡(jiǎn)單的類(lèi),靜態(tài)方法輸出一段 Log:
public class BundleUtil {
public static void printLog(){
Log.e("Bundle","I am a class in the Bundle");
}
}
現(xiàn)在我們的宿主模塊并沒(méi)有在build.gradle中 compile 這個(gè)模塊,我們要手動(dòng)在宿主 apk 中加載并調(diào)用這個(gè)類(lèi)?,F(xiàn)在我們?cè)谒拗髂K的 MainActivity 中的 onCreate() 方法中實(shí)現(xiàn)加載邏輯。
protect void onCreate(Bundle savedInstanceState){
//省略一些代碼
...
String apkPath = getExternalCacheDir().getAbsolutePath()+"/bundle-debug.apk";
loadApk(apkPath);
}
private void loadApk(String apkPath) {
//應(yīng)用內(nèi)部目錄,MODE_PRIVATE 代表只有自己應(yīng)用可以訪(fǎng)問(wèn)這個(gè)路徑。
File optDir = getDir("opt", MODE_PRIVATE);
//初始化 classLoader,通過(guò) DexClassLoader 來(lái)加載指定目錄下的插件中的類(lèi)
DexClassLoader classLoader = new DexClassLoader(apkPath,
optDir.getAbsolutePath(), null, this.getClassLoader());
try {
//獲取指定路徑插件的 class 字節(jié)碼文件
Class cls = classLoader.loadClass("org.sojex.stockquotes.bundle.BundleUtil");
if (cls != null) {
Object instance = cls.newInstance();
Method method = cls.getMethod("printLog");
method.invoke(instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
這里apkPath指的是插件 apk 存放的路徑,我們把app.bundle通過(guò)./gradlew assembleDebug指令打出 apk 包,并通過(guò)adb push命令推送到手機(jī)上的 apkPath 對(duì)應(yīng)的目錄中。
loadApk()方法是我們的核心方法,我們傳入一個(gè)apkPath參數(shù),指定插件 apk 存放的路徑,再通過(guò)Context.getDir獲取一個(gè)應(yīng)用內(nèi)部路徑,使用這兩個(gè)參數(shù),可以新建一個(gè)DexClassLoader對(duì)象,我們之前講過(guò),DexClassLoader可以加載沒(méi)有安裝的 apk 文件中的類(lèi),通過(guò)它的loadClass方法,獲取到BundleUtil的字節(jié)碼文件。最后通過(guò)反射,即可調(diào)用到插件 apk 類(lèi)中的 printLog()方法。我們運(yùn)行宿主 apk ,發(fā)現(xiàn)結(jié)果成功的打印了出來(lái)。
我們下面自定義一個(gè) ClassLoader。
public class CustomClassLoader extends DexClassLoader {
public CustomClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
}
/**
* 定義 ClassLoder 要以何種策略加載 class 文件
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData != null) {
return defineClass(name, classData, 0, classData.length);
} else {
throw new ClassNotFoundException();
}
}
private byte[] getClassData(String name) {
try {
InputStream inputStream = new FileInputStream(name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = -1;
while ((bytesNumRead = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
自定義一個(gè) ClassLoader 最核心的就是重寫(xiě)findClass方法,方法里要定義我們要以何種策略加載 class 文件?,F(xiàn)在我們沒(méi)有什么特殊的策略,
這里就是通過(guò)指定類(lèi)的路徑加載字節(jié)碼,最后通過(guò)獲取到的字節(jié)碼轉(zhuǎn)化為 class 對(duì)象,getClassData方法就是一個(gè)簡(jiǎn)單的文件讀取。這里只是一個(gè)模擬如何定義 ClassLoader。通過(guò)自定義 ClassLoader,表明插件化框架可以為每一個(gè)插件維護(hù)一個(gè) ClassLoader,在加載普通類(lèi)的時(shí)候就會(huì)繞過(guò) Android 系統(tǒng)的加載機(jī)制,即使沒(méi)有安裝這些插件 apk,我們依然能加載其中的類(lèi)。
因?yàn)?Android 系統(tǒng)在加載 apk 的時(shí)候會(huì)創(chuàng)建一個(gè) PathClassLoader,而插件 apk 的加載繞過(guò)了 Android 系統(tǒng),所以我們就要手動(dòng)的為每一個(gè)插件 apk 都要?jiǎng)?chuàng)建一個(gè) ClassLoader。不僅如此,如果宿主和各插件 apk 中有同名類(lèi),如果不為每個(gè)插件創(chuàng)建 ClassLoader,那么如果該同名類(lèi)已經(jīng)被 ClassLoader 加載過(guò),其他的同名類(lèi)就無(wú)法再被加載了,而不同的 ClassLoader 的同名類(lèi)不會(huì)被判定為同一個(gè)類(lèi),插件中的同名類(lèi)在調(diào)用的時(shí)候依然會(huì)被加載。
當(dāng)然,真正的商業(yè)插件化框架不會(huì)這么簡(jiǎn)單,類(lèi)加載模塊不僅要完成類(lèi)的查找和加載,還要對(duì)插件的 ClassLoader 進(jìn)行管理,確保所有類(lèi)都能加載。
那么下一篇文章我們將寫(xiě)一個(gè)簡(jiǎn)單的插件管理器來(lái)模擬插件化框架的管理步驟。
本文部分內(nèi)容參考于慕課網(wǎng)實(shí)戰(zhàn)課程「Android 應(yīng)用發(fā)展趨勢(shì)必備武器 熱修復(fù)與插件化」,有興趣的朋友可以付費(fèi)學(xué)習(xí)。
插件化實(shí)戰(zhàn)課程