应用程序使用的ClassLoader都是PathClassLoader类的实例。那么,这个PathClassLoader是什么呢?从Android SDK给出的源码只能看出这么多:
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
发现其实这个类真的就这么多内容;我们继续查看它的父类BaseDexClassLoader;ClassLoader嘛,我们查看findClass或者defineClass方法,BaseDexClassLoader的findClass方法如下:
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
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;
}
可以看到,查找Class的任务通过pathList完成;这个pathList是一个DexPathList类的对象,它的findClass方法如下:
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
这个DexPathList内部有一个叫做dexElements的数组,然后findClass的时候会遍历这个数组来查找Class;如果我们把插件的信息塞进这个数组里面,那么不就能够完成类的加载过程吗?!!
通过上述分析,我们知道,可以把插件的相关信息放入BaseDexClassLoader的表示dex文件的数组里面,这样宿主程序的ClassLoader在进行类加载,遍历这个数组的时候,会自动遍历到我们添加进去的插件信息,从而完成插件类的加载!
接下来,我们实现这个过程;我们会用到一些较为复杂的反射技术哦~不过代码非常短:
public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// 获取 BaseDexClassLoader : pathList
Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathListObj = pathListField.get(cl);
// 获取 PathList: Element[] dexElements
Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements");
dexElementArray.setAccessible(true);
Object[] dexElements = (Object[]) dexElementArray.get(pathListObj);
// Element 类型
Class<?> elementClass = dexElements.getClass().getComponentType();
// 创建一个数组, 用来替换原始的数组
Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);
// 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数
Constructor<?> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);
Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0));
Object[] toAddElementArray = new Object[] { o };
// 把原始的elements复制进去
System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
// 插件的那个element复制进去
System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);
// 替换
dexElementArray.set(pathListObj, newElements);
}
参考文章:http://weishu.me/2016/04/05/understand-plugin-framework-classloader/