1.前言
插件化技术最初源于免安装运行 apk 的想法,这个免安装的 apk 就可以理解为插件,而支持插件的 app 我们一般叫宿主。宿主可以在运行时加载和运行插件,这样便可以将 app 中一些不常用的功能模块做成插件,一方面减小 了安装包的大小,另一方面可以实现 app 功能的动态扩展。
2.插件化的实现
我们如何去实现一个插件化呢?
首先我们要知道,插件apk是没有安装的,那我们怎么加载它呢?不知道。。。
一个 apk 主要就是由代码和资源组成,所以上面的问题我们可以变为:如何加载插件的类?如何加载插件的资源?这样的话是不是就有眉目了。
然后我们还需要解决类的调用的问题,这个地方主要是四大组件的调用问题。我们都知道,四大组件是需要注册 的,而插件的四大组件显然没有注册,那我们怎么去调用呢?
所以我们接下来就是解决这三个问题,从而实现插件化
-
- 如何加载插件的类?
-
- 如何加载插件的资源?
-
- 如何调用插件类
3.类加载
- 我们在学 java 的时候知道,java 源码文件编译后会生成一个 class 文件,而在 Android 中,将代码编译后会生成一个 apk 文件,将 apk 文件解压后就可以看到其中有一个或多个 classes.dex 文件,它就是安卓把所有 class 文件进行合并,优化后生成的。
- java 中 JVM 加载的是 class 文件,而安卓中 DVM 和 ART 加载的是 dex 文件,虽然二者都是用的 ClassLoader 加载的,但因为加载的文件类型不同,还是有些区别的,所以接下来我们主要介绍安卓的 ClassLoader 是如何加载dex 文件的。
3.1 ClassLoader的实现类
ClassLoader是一个抽象类,实现类主要分为两种类型:系统类加载器和自定义加载器。 其中系统类加载器主要包括三种:
- BootClassLoader 用于加载Android Framework层class文件。
- PathClassLoader 用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex
- DexClassLoader 用于加载指定的dex,以及jar、zip、apk中的classes.dex
parent关系:
DexClassLoader —》PathClassLoader —》BootClassLoader
3.2 加载原理
假设我们有一个apk文件,路径是 apkPath,然后里面有个类 com.android.first.Test,那么我们可以通过如下方式去加载 Test 类:
DexClassLoader dexClassLoader = new DexClassLoader(dexPath,context.getCacheDir().getAbsolutePath(), null, context.getClassLoader());
Class<?> clazz = dexClassLoader.loadClass("com.android.first.Test");
因为我们需要将插件的 dex 文件加载到宿主里面,所以我们接下来分析源码,看 DexClassLoader 类加载器到底是怎么加载一个 apk 的 dex 文件的。
通过查找发现,DexClassLoader 类中没有 loadClass 方法,一路向上查找,最后在 ClassLoader 类中找到了改方法,(api28)源码如下:
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;
}
首先检测这个类是否已经被加载了,如果已经加载了,直接获取并返回。如果没有被加载,parent 不为 null,则调用parent的loadClass进行加载,依次递归,如果找到了或者加载了就返回,如果即没找到也加载不了,才自己 去加载。这个过程就是 双亲委托机制。
由上面可知,BootCloassLoader是最后一个加载器,所以接下来看下它是如何结束向上递归查找的。
class BootClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null);
}
@Override
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
clazz = findClass(className);
}
return clazz;
}
}
在BootCloassLoader类中重写了findClass和loadClass方法,并且在loadClass方法中,不再获取parent,从而结束递归。
接着我们再来看下,在所有parent都没加载成功的情况下,DexClassLoader是如何加载的。通过查找我们发现在他的父类BaseDexClassLoader中,重写了findClass方法。
public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
// TODO We should support giving this a library search path maybe.
super(parent);
//初始化 pathList
this.pathList = new DexPathList(this, dexFiles);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
//在pathList中查找指定的ClassLoader
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;
}
接着再来看DexPathList类中的findClass方法。
private Element[] dexElements;
public Class<?> findClass(String name, List<Throwable> suppressed) {
// 通过Element获取Class对象
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
3.3 插件化原理总结
在这可以看到,Class对象就是从Element中获取的,而每一个Element就对应一个dex文件,因为我们的dex文件可能有多个,所有这是个数组Element[]。到这里,插件化就可以分为以下几步:
- 1、创建插件的DexClassLoader类加载器,然后通过反射获取插件的dexElements值。
- 2、创建宿主的PathClassLoader类加载器,然后通过反射获取宿主的dexElements值。
- 3、合并宿主的dexElements与插件的dexElements,生成新的Elements[]。
- 4、最后通过反射将新的Elements[]赋值给宿主的dexElements。
3.4 插件化代码总结
public static void loadClass(Context context) {
// 获取 pathList 的字段
Class baseDexClassLoaderClass = null;
try {
baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = baseDexClassLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
// 获取 dexElements 字段
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
// 拿到宿主的dexElements
ClassLoader hostClassload = context.getClassLoader();
Object hostPathlist = pathListField.get(hostClassload);
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathlist);
//拿到插件的dexElements
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, null, context.getCacheDir().getAbsolutePath(), hostClassload);
Object pluginPathList = pathListField.get(dexClassLoader);
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
//创建新的数组
Object[] newElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), hostDexElements.length + pluginDexElements.length);
//填充数据
System.arraycopy(hostDexElements, 0, newElements, 0, hostDexElements.length);
System.arraycopy(pluginDexElements, 0, newElements, hostDexElements.length, pluginDexElements.length);
//覆盖系统的 hostDexElements
//将生成的新值赋给“dexElements”
//hostDexElements -》newElements
dexElementsField.set(hostDexElements, newElements);
} catch (Exception e) {
e.printStackTrace();
}
}
到此,插件化原理告一段落。后续更新请稍等哈。
`