大話插件化系列目錄
插件化(一) 插件化思想與類加載
插件化(二) 插件化Activity的啟動
插件化(三) 插件資源加載
最開始的起源:插件化技術(shù)最初源于免安裝運行 apk 的想法。
免安裝的 apk 我們稱它為 插件
支持插件的 app 我們稱它為 宿主
免安裝的 apk 我們稱它為 插件
支持插件的 app 我們稱它為 宿主
插件話解決的問題
- APP的功能模塊越來越多,體積越來越大
- 模塊之間的耦合度高,協(xié)同開發(fā)溝通成本越來越大
- 方法數(shù)目可能超過65535,APP占用的內(nèi)存過大
- 應用之間的互相調(diào)用
由于維護成本高,技術(shù)難點大,大公司一線公司用的比較多,而且兼容問題比較多,所以維護起來難點大。
插件話與組件化, 模塊化的區(qū)別
組件化開發(fā)就是將一個app分成多個模塊,每個模塊都是一個組件,開發(fā)的 過程中我們可以讓這些組件相互依賴或者單獨調(diào)試部分組件等,但是最終發(fā) 布的時候是將這些組件合并統(tǒng)一成一個apk,這就是組件化開發(fā)。
再具體一些,就是 組件化分模塊縱向依賴公共庫,橫向彼此之間沒有直接依賴關系。
插件化開發(fā)和組件化略有不同,插件化開發(fā)是將整個app拆分成多個模塊, 這些模塊包括一個宿主和多個插件,每個模塊都是一個apk,最終打包的時 候宿主apk和插件apk分開打包。
模塊化,組件化和模塊化似乎類似。但是目的不一樣,模塊話是業(yè)務為主,用業(yè)務劃分模塊,但是傳統(tǒng)的這種做法導致多個業(yè)務關聯(lián)耦合。
插件話的實現(xiàn)思路,面臨的幾個難題
- 如何加載插件的類?
- 如何啟動插件的四大組件?
- 如何加載插件的資源?
可以做的功能,換膚,熱修復,多開,ABTest
類聲明周期簡單看
我們抽象一個類Person
我們抽象一個類Car
這些都是類Class
我們的Class也是類Class
加載------> 驗證 -----> 準備------> 解析
|->初始化->使用->卸載
加載階段,虛擬機做三件事:
1.通過一個類的全限定名來獲取定義此類的二 進制字節(jié)流。
2.將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為 方法區(qū)域的運行時數(shù)據(jù)結(jié)構(gòu)。
3.在Java堆中生成一個代表這個類的Class對象, 作為方法區(qū)域數(shù)據(jù)的訪問入口
為什么我們說反射會有一定的降低效率
- 產(chǎn)生大量的臨時對象
- 檢查可見性
- 會生成字節(jié)碼 --- 沒有優(yōu)化
- 類型轉(zhuǎn)換
ClassLoader 繼承的關系

PathClassLoader & DexClassLoader
在8.0(API 26)之前,它們二者的唯一區(qū)別是 第二個參數(shù) optimizedDirectory,這個參數(shù)的意 思是生成的 odex(優(yōu)化的dex)存放的路徑。
在8.0(API 26)及之后,二者就完全一樣了。
高版本合并了,所以區(qū)別不大了
這就是兼容問題,以后有沒有,每次更新都要查看,所以說維護成本高
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
package dalvik.system;
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
寫一個測試代碼:
private void printClassLoader(){
ClassLoader classLoader = getClassLoader();
while (classLoader != null) {
Log.e("zcw_plugin", "classLoader:" + classLoader);
classLoader = classLoader.getParent();
}
//pathClassLoader 和 BootClassLoader 分別加載什么類
Log.e("zcw_plugin", "Activity 的 classLoader:" + Activity.class.getClassLoader());
Log.e("zcw_plugin", "Activity 的 classLoader:" + AppCompatActivity.class.getClassLoader());
}
打印
2020-11-25 12:18:01.911 7566-7566/top.zcwfeng.plugin E/zcw_plugin: classLoader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/top.zcwfeng.plugin-FzOugxhVZye2nIoxyC1IPg==/base.apk"],nativeLibraryDirectories=[/data/app/top.zcwfeng.plugin-FzOugxhVZye2nIoxyC1IPg==/lib/x86, /system/lib]]]
2020-11-25 12:18:01.911 7566-7566/top.zcwfeng.plugin E/zcw_plugin: classLoader:java.lang.BootClassLoader@e9e6661
2020-11-25 12:18:01.911 7566-7566/top.zcwfeng.plugin E/zcw_plugin: Activity 的 classLoader:java.lang.BootClassLoader@e9e6661
2020-11-25 12:18:01.912 7566-7566/top.zcwfeng.plugin E/zcw_plugin: Activity 的 classLoader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/top.zcwfeng.plugin-FzOugxhVZye2nIoxyC1IPg==/base.apk"],nativeLibraryDirectories=[/data/app/top.zcwfeng.plugin-FzOugxhVZye2nIoxyC1IPg==/lib/x86, /system/lib]]]
PathClassLoader --》 parent(ClassLoader類型的對象),BootClassLoader 沒有parent
PathClassLoader --- 應用的 類 -- 第三方庫
BootClassLoader --- SDK的類
Activity 是SDK 而不是FrameWork,而AppCompatActivity 是依賴庫中的
類似Glide 都是第三方集成的依賴。
測試加載dex
dex 的文件生成命令
dx --dex --output=output.dex input.class
dx --dex --output=test.dex top/zcwfeng/plugin/Test.class
----------
source class
package top.zcwfeng.plugin;
import android.util.Log;
public class Test {
public Test() {
}
public static void print() {
Log.e("zcw_plugin", "print:啟動插件中方法");
}
}
load dex
private void testLoadDex(){
DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/test.dex",
MainActivity.this.getCacheDir().getAbsolutePath(),
null,
MainActivity.this.getClassLoader());
try {
Class<?> clazz = dexClassLoader.loadClass("top.zcwfeng.plugin.Test");
Method clazzMethod = clazz.getMethod("print");
clazzMethod.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}
ClassLoader.Java 核心,雙親委派
先判斷是否已經(jīng)加載,如果沒有委派雙親去加載,如果沒有加載出來那么在自己查找
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
作用
1.避免重復加載
2.安全考慮,不能攥改

Hook 點
查找 Hook 反射 啟動插件的類
一個dexFile -> 對應一個dex文件
Element --> 對應 dexFile 而 一個APK-> 多個dex文件
Elements[] dexElements ---> 一個app的所有class文件都在dexElements 里面
關注這些類的流程
ClassLoader----DexPathList---Element----DexFile----BootClassLoader---VMClassLoader----Class
因為 宿主的MainActivity 在 宿主 的 dexElements 里面
1.獲取宿主dexElements
2.獲取插件dexElements
3.合并兩個dexElements
4.將新的dexElements 賦值到 宿主dexElements

ps:熱修復原理類似,就是更換加載順序,把修復好的elements放在未曾修復的前面加載,就不會在加載一個錯誤的了
目標:dexElements -- DexPathList類的對象 -- BaseDexClassLoader的對象,類加載器
獲取的是宿主的類加載器 --- 反射 dexElements 宿主
獲取的是插件的類加載器 --- 反射 dexElements 插件
public
class LoadUtil {
private final static String apkPath = "/sdcard/plugin-debug.apk";
public static void load(Context context) {
/**
* 宿主dexElements = 宿主dexElements + 插件dexElements
*
* 1.獲取宿主dexElements
* 2.獲取插件dexElements
* 3.合并兩個dexElements
* 4.將新的dexElements 賦值到 宿主dexElements
*
* 目標:dexElements -- DexPathList類的對象 -- BaseDexClassLoader的對象,類加載器
*
* 獲取的是宿主的類加載器 --- 反射 dexElements 宿主
*
* 獲取的是插件的類加載器 --- 反射 dexElements 插件
*/
try {
Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = clazz.getDeclaredField("pathList");// 只和類有關和對象無關
pathListField.setAccessible(true);
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
// 宿主的類加載器
ClassLoader pathClassLoader = context.getClassLoader();
// DexPathList 類對象
Object hostPathList = pathListField.get(pathClassLoader);
// 宿主的dexElements
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);
// plugin的類加載器
ClassLoader dexClassLoader = new DexClassLoader(apkPath,
context.getCacheDir().getAbsolutePath(),
null
, pathClassLoader);//parent 考慮適配問題,不要傳null
// DexPathList 類對象
Object pluginPathList = pathListField.get(dexClassLoader);
// plugin的dexElements
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
//將新的dexElements 賦值到 宿主dexElements
// 不能直接Object[] obj = new Object[] 因為我們要把obj放到反射的elements里面去,所以不行
Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),
hostDexElements.length + pluginDexElements.length);
System.arraycopy(hostDexElements, 0, newDexElements, 0,
hostDexElements.length);
System.arraycopy(pluginDexElements, 0, newDexElements, hostDexElements.length,
pluginDexElements.length);
//賦值
dexElementsField.set(hostPathList, newDexElements);
} catch (Exception e) {
e.printStackTrace();
}
}
}
加載 apk插件在application
LoadUtil.load(this);
寫測試方法
private void testLoadDex(){
DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/test.dex",
MainActivity.this.getCacheDir().getAbsolutePath(),
null,
MainActivity.this.getClassLoader());
try {
Class<?> clazz = dexClassLoader.loadClass("top.zcwfeng.plugin.Test");
Method clazzMethod = clazz.getMethod("print");
clazzMethod.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}
各大插件的介紹和對比
我們在選擇開源框架的時候,需要根據(jù)自身的需求來,如果加載的插件不需要和宿主有任何耦合,也無須和宿主進行通信,比如加載第三方 App,那么推薦使用 RePlugin,其他的情況推薦使用 VirtualApk。
| 特性 | DynamicAPK | dynamic-load-apk | Small | DroidPlugin | RePlugin | VirtualAPK |
|---|---|---|---|---|---|---|
| 支持四大組件 | 只支持Activity | 只支持Activity | 只支持Activity | 全支持 | 全支持 | 全支持 |
| 組件無需在宿主manifest中預注冊 | × | √ | √ | √ | √ | √ |
| 插件可以依賴宿主 | √ | √ | √ | × | √ | √ |
| 支持PendingIntent | × | × | × | √ | √ | √ |
| Android特性支持 | 大部分 | 大部分 | 大部分 | 幾乎全部 | 幾乎全部 | 幾乎全部 |
| 兼容性適配 | 一般 | 一般 | 中等 | 高 | 高 | 高 |
| 插件構(gòu)建 | 部署aapt | 無 | Gradle插件 | 無 | Gradle插件 | Gradle插件 |