市场上热修复有两种一种是基于multidex的更新修复(比如tinker),另外一种是native hook(比如dexposed),tinker这种是反射获取dexelements数组,修改dex加载顺序。今天我们主要介绍dex这种。
热修复包括两个部分
- 从远程端下载修复好bug的补丁包
- 客户端安装补丁包,加载补丁包的类。
使用android�类加载器,在类没被加载到模拟器前(一般在application热修复,如果类已加载,再去记载相同的类就无效了)然后先加载补丁dex,再去加载原来的app里面的dex,因为加载过的类
不会被加载第二次,从而做到热修复。
1. 类加载器
android的类加载器分为PathClassLoader和DexClassLoader.他们两者之间的区别就是
- PathClassLoader只能加载安装到手机里面的dex(比如data/app/包名里面的dex)
- DexClassLoader 可以加载任意目录下的dex/jar/apk/zip.
下面我们从源码看看他们到底有什么不同
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
//分割线
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
从代码可以看到两个classloader都继承了BaseDexClassLoader
调用了父类的构造方法,dexclassloader多传入了一个optimizedDirectory文件,然后从注解可以看到optimizedDirectory参数是dex输出的文件。接下来我们再看basedexclassloader里面做了什么骚操作。
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@Override
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;
}
@Override
protected URL findResource(String name) {
return pathList.findResource(name);
}
@Override
protected Enumeration<URL> findResources(String name) {
return pathList.findResources(name);
}
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
....
BaseDexClassLoader的构造参数分别是
- 一系列的补丁包路径
- 补丁包输出路径
- 加载用到库的路径,比如c 那些native库
- 父加载器
其中DexPathList通过这四个参数完成初始化。最后通过findclass方法 里面的DexPathList.findClass来返回类。
2. DexPathList源码
接着看DexPathList源码里面做了什么
首先看构造函数
Element[] dexElements;
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
//省略参数的判断
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
从代码可以看到给classloader赋值,创建一个异常的list,给dexelements赋值,这里的dexelements是一个数组。来看看splitDexPath到底做什么了。
看到splitAndAdd方法会根据:来截取字符串,就是多个dexpath之间用:分割,然后变成file,被加进去List<File> flies里面。最后看makeDexElements方法,
遍历files,首先判断是否文件夹,是的话全部加进去elements,最后装转成ELement数组。
最后在DexPathList的findclass方法,将Element的数组每个Element对象的dex加载成class。
所以我们在什么时候插入补丁包的dex呢,就在Dexpatchlist类的findclass方法里面。将补丁的Elements数组加上原来的Elements数组,一起循环获取dex返回class。我们可以通过反射去获取elements数组。然后合并数组。调用findclass方法。以上就是multidex热修复的原理。
网友评论