大話(huà)插件化系列目錄
插件化(一) 插件化思想與類(lèi)加載
插件化(二) 插件化Activity的啟動(dòng)
插件化(三) 插件資源加載
最開(kāi)始的起源:插件化技術(shù)最初源于免安裝運(yùn)行 apk 的想法地梨。
免安裝的 apk 我們稱(chēng)它為 插件
支持插件的 app 我們稱(chēng)它為 宿主
免安裝的 apk 我們稱(chēng)它為 插件
支持插件的 app 我們稱(chēng)它為 宿主
插件話(huà)解決的問(wèn)題
- APP的功能模塊越來(lái)越多觉阅,體積越來(lái)越大
- 模塊之間的耦合度高,協(xié)同開(kāi)發(fā)溝通成本越來(lái)越大
- 方法數(shù)目可能超過(guò)65535口予,APP占用的內(nèi)存過(guò)大
- 應(yīng)用之間的互相調(diào)用
由于維護(hù)成本高,技術(shù)難點(diǎn)大涕侈,大公司一線(xiàn)公司用的比較多沪停,而且兼容問(wèn)題比較多,所以維護(hù)起來(lái)難點(diǎn)大裳涛。
插件話(huà)與組件化, 模塊化的區(qū)別
組件化
開(kāi)發(fā)就是將一個(gè)app分成多個(gè)模塊木张,每個(gè)模塊都是一個(gè)組件,開(kāi)發(fā)的 過(guò)程中我們可以讓這些組件相互依賴(lài)或者單獨(dú)調(diào)試部分組件等端三,但是最終發(fā) 布的時(shí)候是將這些組件合并統(tǒng)一成一個(gè)apk舷礼,這就是組件化開(kāi)發(fā)。
再具體一些郊闯,就是 組件化分模塊縱向依賴(lài)公共庫(kù)妻献,橫向彼此之間沒(méi)有直接依賴(lài)關(guān)系蛛株。
插件化
開(kāi)發(fā)和組件化略有不同,插件化開(kāi)發(fā)是將整個(gè)app拆分成多個(gè)模塊育拨, 這些模塊包括一個(gè)宿主和多個(gè)插件谨履,每個(gè)模塊都是一個(gè)apk,最終打包的時(shí) 候宿主apk和插件apk分開(kāi)打包熬丧。
模塊化
笋粟,組件化和模塊化似乎類(lèi)似。但是目的不一樣析蝴,模塊話(huà)是業(yè)務(wù)為主害捕,用業(yè)務(wù)劃分模塊,但是傳統(tǒng)的這種做法導(dǎo)致多個(gè)業(yè)務(wù)關(guān)聯(lián)耦合闷畸。
插件話(huà)的實(shí)現(xiàn)思路尝盼,面臨的幾個(gè)難題
- 如何加載插件的類(lèi)?
- 如何啟動(dòng)插件的四大組件?
- 如何加載插件的資源?
可以做的功能,換膚腾啥,熱修復(fù)东涡,多開(kāi),ABTest
類(lèi)聲明周期簡(jiǎn)單看
我們抽象一個(gè)類(lèi)Person
我們抽象一個(gè)類(lèi)Car
這些都是類(lèi)Class
我們的Class也是類(lèi)Class
加載------> 驗(yàn)證 -----> 準(zhǔn)備------> 解析
|->初始化->使用->卸載
加載階段倘待,虛擬機(jī)做三件事:
1.通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取定義此類(lèi)的二 進(jìn)制字節(jié)流疮跑。
2.將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為 方法區(qū)域的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
3.在Java堆中生成一個(gè)代表這個(gè)類(lèi)的Class對(duì)象凸舵, 作為方法區(qū)域數(shù)據(jù)的訪(fǎng)問(wèn)入口
為什么我們說(shuō)反射會(huì)有一定的降低效率
- 產(chǎn)生大量的臨時(shí)對(duì)象
- 檢查可見(jiàn)性
- 會(huì)生成字節(jié)碼 --- 沒(méi)有優(yōu)化
- 類(lèi)型轉(zhuǎn)換
ClassLoader 繼承的關(guān)系
PathClassLoader & DexClassLoader
在8.0(API 26)之前祖娘,它們二者的唯一區(qū)別是 第二個(gè)參數(shù) optimizedDirectory,這個(gè)參數(shù)的意 思是生成的 odex(優(yōu)化的dex)存放的路徑啊奄。
在8.0(API 26)及之后渐苏,二者就完全一樣了。
高版本合并了菇夸,所以區(qū)別不大了
這就是兼容問(wèn)題琼富,以后有沒(méi)有,每次更新都要查看庄新,所以說(shuō)維護(hù)成本高
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);
}
}
寫(xiě)一個(gè)測(cè)試代碼:
private void printClassLoader(){
ClassLoader classLoader = getClassLoader();
while (classLoader != null) {
Log.e("zcw_plugin", "classLoader:" + classLoader);
classLoader = classLoader.getParent();
}
//pathClassLoader 和 BootClassLoader 分別加載什么類(lèi)
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類(lèi)型的對(duì)象)鞠眉,BootClassLoader 沒(méi)有parent
PathClassLoader --- 應(yīng)用的 類(lèi) -- 第三方庫(kù)
BootClassLoader --- SDK的類(lèi)
Activity 是SDK 而不是FrameWork,而AppCompatActivity 是依賴(lài)庫(kù)中的
類(lèi)似Glide 都是第三方集成的依賴(lài)。
測(cè)試加載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:啟動(dòng)插件中方法");
}
}
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)加載择诈,如果沒(méi)有委派雙親去加載械蹋,如果沒(méi)有加載出來(lái)那么在自己查找
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.避免重復(fù)加載
2.安全考慮,不能攥改
Hook 點(diǎn)
查找 Hook 反射 啟動(dòng)插件的類(lèi)
一個(gè)dexFile -> 對(duì)應(yīng)一個(gè)dex文件
Element --> 對(duì)應(yīng) dexFile 而 一個(gè)APK-> 多個(gè)dex文件
Elements[] dexElements ---> 一個(gè)app的所有class文件都在dexElements 里面
關(guān)注這些類(lèi)的流程
ClassLoader----DexPathList---Element----DexFile----BootClassLoader---VMClassLoader----Class
因?yàn)?宿主的MainActivity 在 宿主 的 dexElements 里面
1.獲取宿主dexElements
2.獲取插件dexElements
3.合并兩個(gè)dexElements
4.將新的dexElements 賦值到 宿主dexElements
ps:熱修復(fù)原理類(lèi)似羞芍,就是更換加載順序哗戈,把修復(fù)好的elements放在未曾修復(fù)的前面加載,就不會(huì)在加載一個(gè)錯(cuò)誤的了
目標(biāo):dexElements -- DexPathList類(lèi)的對(duì)象 -- BaseDexClassLoader的對(duì)象荷科,類(lèi)加載器
獲取的是宿主的類(lèi)加載器 --- 反射 dexElements 宿主
獲取的是插件的類(lèi)加載器 --- 反射 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.合并兩個(gè)dexElements
* 4.將新的dexElements 賦值到 宿主dexElements
*
* 目標(biāo):dexElements -- DexPathList類(lèi)的對(duì)象 -- BaseDexClassLoader的對(duì)象唯咬,類(lèi)加載器
*
* 獲取的是宿主的類(lèi)加載器 --- 反射 dexElements 宿主
*
* 獲取的是插件的類(lèi)加載器 --- 反射 dexElements 插件
*/
try {
Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = clazz.getDeclaredField("pathList");// 只和類(lèi)有關(guān)和對(duì)象無(wú)關(guān)
pathListField.setAccessible(true);
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
// 宿主的類(lèi)加載器
ClassLoader pathClassLoader = context.getClassLoader();
// DexPathList 類(lèi)對(duì)象
Object hostPathList = pathListField.get(pathClassLoader);
// 宿主的dexElements
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);
// plugin的類(lèi)加載器
ClassLoader dexClassLoader = new DexClassLoader(apkPath,
context.getCacheDir().getAbsolutePath(),
null
, pathClassLoader);//parent 考慮適配問(wèn)題纱注,不要傳null
// DexPathList 類(lèi)對(duì)象
Object pluginPathList = pathListField.get(dexClassLoader);
// plugin的dexElements
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
//將新的dexElements 賦值到 宿主dexElements
// 不能直接Object[] obj = new Object[] 因?yàn)槲覀円裲bj放到反射的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);
寫(xiě)測(cè)試方法
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();
}
}
各大插件的介紹和對(duì)比
我們?cè)谶x擇開(kāi)源框架的時(shí)候胆胰,需要根據(jù)自身的需求來(lái)奈附,如果加載的插件不需要和宿主有任何耦合,也無(wú)須和宿主進(jìn)行通信煮剧,比如加載第三方 App,那么推薦使用 RePlugin将鸵,其他的情況推薦使用 VirtualApk勉盅。
特性 | DynamicAPK | dynamic-load-apk | Small | DroidPlugin | RePlugin | VirtualAPK |
---|---|---|---|---|---|---|
支持四大組件 | 只支持Activity | 只支持Activity | 只支持Activity | 全支持 | 全支持 | 全支持 |
組件無(wú)需在宿主manifest中預(yù)注冊(cè) | × | √ | √ | √ | √ | √ |
插件可以依賴(lài)宿主 | √ | √ | √ | × | √ | √ |
支持PendingIntent | × | × | × | √ | √ | √ |
Android特性支持 | 大部分 | 大部分 | 大部分 | 幾乎全部 | 幾乎全部 | 幾乎全部 |
兼容性適配 | 一般 | 一般 | 中等 | 高 | 高 | 高 |
插件構(gòu)建 | 部署aapt | 無(wú) | Gradle插件 | 無(wú) | Gradle插件 | Gradle插件 |