一、发展历史
插件化技术最初源于免安装运行apk的想法,这个免安装的apk可以理解为插件。支持插件化的app可以在运行时加载和运行插件,这样便可以将app中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现app功能的动态扩展。想要实现插件化,主要是解决下面三个问题:
插件中代码的加载和与主工程的互相调用
插件中资源的加载和与主工程的互相访问
四大组件生命周期的管理
二、插件化原理
1、类加载机制和插件加载方法
我们熟悉的ClassLoader有:
BootClassLoader:加载系统的类
PathClassLoader:加载已安装的apk类
DexClassLoader:自定义加载jar、dex的类
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);
}
}
// DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
每一个ClassLoader有一个父ClassLoader(组合关系),尝试加载一个类时,会先让父亲去加载。
loadClass方法实现了双亲委托机制:父Classloader无法加载类时,再调用自己的findClass方法尝试加载类。
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
//首先从已经加载的类中查找
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
//如果没有加载过,先调用父加载器的loadClass
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
//父加载器都没有加载,则尝试加载
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
插件类加载的两种方案
方案1:让宿主的classloader去加载插件类
原理:classloader.findClass方法是从一个pathList中加载类,我们把插件的路径添加到这个List中
好处:实现简单
方案2 : 构建插件单独的ClassLoader
原理:每个插件创建一个单独的classloader,Hook系统classloader,更改loadClass逻辑:先尝试从宿主classloader中加载类,再尝试从每个插件中加载类
实现:见下方
2、加载资源
如何加载资源
Android系统通过Resource对象加载资源,因此只需要添加资源(即apk文件)所在路径到AssetManager中,即可实现对插件资源的访问。
// 创建AssetManager对象
AssetManager assetManager = new AssetManager();
// 将apk路径添加到AssetManager中
if (assetManager.addAssetPath(apkPath) == 0) {
return null;
}
// 创建插件Resource对象
Resources pluginResources = new Resources(assetManager, metrics, getConfiguration());
由于AssetManager的构造方法时hide的,需要通过反射区创建。
如何固定资源ID
原理:android在资源编译过程中预留了固定id的方法,用于对诸如对外发布的jar包组件中引用的资源做固定处理以保障版本对jar包的兼容性。
方法:将需要固定的资源以及id写在一个public.xml中,放置于res/values/public.xml中,这样在编译时相关的资源id就固定为xml中定义的id,可以支持几乎所有的R文件资源类型,定义如下:
部分代码如下:
<public type="id" name="background" id="0x01020000" />
<public type="id" name="checkbox" id="0x01020001" />
<public type="id" name="content" id="0x01020002" />
格式是 :
<public type="资源类型" name="资源名" id="0x7f080000" />
如何检测插件依赖宿主的资源
原理:读取插件apk的dex、xml、resources.asrc中的id,判断如果是0x7开头则是宿主的资源
xml 直接读文件,判断是否匹配 0x7000000 (${prefix}(?:[0-9a-f]){6})
resources.asrc 直接读文件,判断是否匹配 0x7000000 (${prefix}(?:[0-9a-f]){6})
dex转成smali之后,判断 R$layout;-> R\$((?:drawable|dimen|bool|color|string|raw|integer|layout|mipmap|style|anim|attr);->.+):