插件化讲解到实战1–类加载的解析和双亲委派机制
插件化讲解到实战2–调用插件中的类
插件化讲解到实战3–Hook的源码分析和启动插件的Activity
插件化讲解到实战4–启动Activity适配9.0,10.0等版本
思路
通过反射直接调用插件的类肯定是调用不到的。通过上一篇知道,每一个dex文件对应一个Element,因为所有的dex文件都在dexElement数组中,那么合并宿主和插件的Elements就可以调用插件的dex,其实就是将插件的dex放到宿主的dexElement数组中,需要通过反射实现。
反射思路:
1、获取宿主的dexElements
2、获取插件的dexElements
3、合并宿主的dexElements和插件的dexElements
4、将合并的dexElements赋值到宿主的dexElements
具体:
1和2思路:目的是获取数组的dexElements->dexElementsField对象->DexPathList对象->pathList的Field->BaseDexClassLoader对象,即数组和插件的类加载器
1、获取宿主的dexElements的代码如下:
//获取dexElementsField
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
//获取pathListField
Class<?> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = classLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
//获取数组的类加载器
ClassLoader pathclassLoader = context.getClassLoader();
//获取DexPathList对象
Object hostPathList = pathListField.get(pathclassLoader);
//获取宿主的dexElements,目的达到!
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);
2、 获取插件的dexElements:
//插件,插件的类加载器需要自己创建
//DexClassLoader四个参数,第一个参数 apk的目录,第二个参数优化路径
//版本7.0和之后第四个参数可以是null(通过查看ClassLoader的源码,loadClass的parent是否为空进行了判断)
ClassLoader pluginClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(), null, null);
//获取DexPathList对象
Object pluginPathList = pathListField.get(pluginClassLoader);
//获取插件的dexElements
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
3、合并宿主和插件的Elements:
//合并
//Elements需要通过反射创建
Object[] newElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), hostDexElements.length + pluginDexElements.length);
System.arraycopy(hostDexElements, 0, newElements, 0, hostDexElements.length);
System.arraycopy(dexElementsField, 0, newElements, hostDexElements.length, pluginDexElements.length);
4、将合并的dexElements赋值到宿主的dexElements
//赋值到数组的dexElements
// hostDexElements = newElements;
dexElementsField.set(hostPathList, newElements);
5、Application中初始化
LoadUtil.loadClass(this);
6、调用类的代码
try {
Class<?> clazz = Class.forName("com.enjoy.plugin.Test");
Method print = clazz.getMethod("print");
print.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
别忘了动态申请权限和在宿主app中的权限。
完整代码:
LoadUtil.java:
LoadUtil.java:
import android.content.Context;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import dalvik.system.DexClassLoader;
public class LoadUtil {
private static final String apkPath = "/sdcard/plugin-debug.apk";
//Context获取数组的类加载器
public static void loadClass(Context context) {
// 宿主的dexElements ---》 dexElementsField --》
// DexPathList对象 --》 pathList的Field --》 **BaseDexClassLoader** 对象
// --》 能不能宿主和插件的类加载器
try {
//获取dexElementsField
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
//获取pathListField
Class<?> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = classLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
//获取数组的类加载器
ClassLoader pathClassLoader = context.getClassLoader();
//获取DexPathList对象
Object hostPathList = pathListField.get(pathClassLoader);
//获取宿主的dexElements,目的达到!
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);
//插件,插件的类加载器需要自己创建
//DexClassLoader四个参数,第一个参数 apk的目录,第二个参数优化路径
//版本7.0和之后第四个参数可以是null(通过查看ClassLoader的源码,loadClass的parent是否为空进行了判断)
ClassLoader pluginClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(), null, null);
//获取DexPathList对象
Object pluginPathList = pathListField.get(pluginClassLoader);
//获取插件的dexElements
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
//合并
//Elements需要通过反射创建
Object[] newElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), hostDexElements.length + pluginDexElements.length);
System.arraycopy(hostDexElements, 0, newElements, 0, hostDexElements.length);
System.arraycopy(dexElementsField, 0, newElements, hostDexElements.length, pluginDexElements.length);
//赋值到数组的dexElements
// hostDexElements = newElements;
dexElementsField.set(hostPathList, newElements);
} catch (Exception e) {
e.printStackTrace();
}
}
}
插件com.enjoy.plugin包下Test.java:
import android.util.Log;
public class Test {
public static void print() {
Log.e("leo", "print: 启动插件方法");
}
}