插件化(一):如何加载插件的类
1.实现思路
Android打包编译过程中会把所有的Java源文件编译成Class文件,然后经过字节码优化处理打包到dex文件中。一个dex文件最多容纳65535个方法,Google推出MutiDex兼容方案,实现原理就是用一个数组存放多个dex文件。
所以,在App启动运行时将我们的插件dexElements与宿主dexElements合并,再将新的dexElements赋值给宿主dexElements。
2.Android类加载机制
要实现插件的加载,需要先了解Android中的类加载机制。
Android类加载主要用到下图 几个类
android_classloader.png
ClassLoader为基类,loadClass方法在基类中实现。
BootClassLoader加载SDK中类。
PathClassLoader与DexClassLoader在Android8.0以后实现的功能完全一致,加载应用的类。
BaseDexClassLoader具体实现findClass过程。
先看loadClass方法:
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;
}
loadClass分析:首先执行findLoadedClass()方法,如果类已经加载过直接返回,如果没有加载过会先判断parent是否为空,如果不为空用parent实例来递归加载类(双亲委派机制),如果parent为空则加载Android sdk中的系统类,如果最后还是为空就会调用findClass方法,findClass方法为ClassLoader空实现,具体实现在BaseDexClassLoader中。
我们接着看BaseDexClassLoader的findClass方法。(http://androidos.net.cn/sourcecode 在线看系统源码) API Level: 28
//BaseDexClassLoader.class
private final DexPathList pathList;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
//——>分析1 pathList.findClass
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
——>分析1 调用了 DexPathList的findCLass方法
//DexPathList.class
package dalvik.system;
private Element[] dexElements;
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
//——>分析2 element.findClass
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
——>分析2 遍历数组dexElements,通过element.findClass查找Class。看下Element:
static class Element {
//...
private final DexFile dexFile;
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
//...
}
一个Element对应一个dex文件,至此我们找到了类的整个加载过程。
所以我们获取目标dexElements的流程:类加载器—>BaseDexClassLoader对象—>DexPathList对象—>目标dexElements。
3.具体实现
public static void loadPlugin(Context context) {
/**
* 宿主dexElements = 宿主dexElements + 插件dexElements
* 1.获取宿主dexElements
* 2.获取插件dexElements
* 3.合并两个dexElements
* 4.将新的dexElements赋值到宿主dexElements
*/
try {
//读取BaseDexClassLoader的pathList字段
Class<?> dexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = dexClassLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
//读取DexPathList中的dexElements字段
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
//(1)获取宿主的dexElements
//1.获取宿主ClassLoader实例
ClassLoader hostClassLoader = context.getClassLoader();
//2.根据ClassLoader实例获取宿主pathList实例
Object hostPathList = pathListField.get(hostClassLoader);
//3.根据宿主pathList实例获取宿主dexElements实例
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);
//(2)获取插件的dexElements
//1.根据插件apk存放的路径来创建插件ClassLoader
DexClassLoader pluginClassLoader = new DexClassLoader(pluginApkPath, context.getCacheDir().getAbsolutePath(), null, hostClassLoader);
//2.根据插件ClassLoader实例获取插件pathList实例
Object pluginPathList = pathListField.get(pluginClassLoader);
//3.根据插件pathList实例获取插件dexElements实例
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
//(3)创建一个新Element数组,合并宿主和插件的dexElements并赋值给宿主的DexElements
//1.通过反射创建一个新Element数组
Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), hostDexElements.length + pluginDexElements.length);
//2.将宿主dexElements拷贝到新dexElements
System.arraycopy(hostDexElements, 0, newDexElements, 0, hostDexElements.length);
//3.将插件dexElements拷贝到新dexElements后面
System.arraycopy(pluginDexElements, 0, newDexElements, hostDexElements.length, pluginDexElements.length);
//4.将合并好的新dexElements赋值给宿主的DexElements
dexElementsField.set(hostPathList, newDexElements);
} catch (Exception e) {
e.printStackTrace();
}
}
调用示例:
LoadUtil.loadPlugin(this)
try {
Class<?> clazz = Class.forName("com.sun.plugin.Test");
Method method = clazz.getMethod("print");
method.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}