惯例段子。
阅读本文你可以掌握,热修复的原理和简单实现.
目录 |
---|
Classloader热修复原理 |
热修复代码实现 |
面试知识 |
Classloader热修复原理
从这个图上能看出什么?
1,PathClassLoader 是Android 默认加载器.
2,BootClassLoader是PathClassLoader 的父加载器(注意不是父类).
原理
1,上一篇classLoader文章介绍过PathClassLoader加载类的过程拿到pathList对象,然后拿到里面的Element数组,进行类的加载.
2,双亲委派机制,如果一个类之前加载过就不会再被加载了.
这两点就是ClassLoader热修复的核心原理.
把修复的dex文件,放到Element数组前边,根据双亲委派机制就会加载修复之后的dex文件了,有问题的dex文件因为双亲委派机制就不会加载了.(本来想画个图,但是画的太丑就没上图)
热修复代码实现
思路
1,PathClassLoader 加载路径我们不能修改,但是DexClassLoader 我们是可以指定加载路径的,这里不做深层说明了,底层根据那个参数去判断的加载目录.(可能这个说法不太对,但是都是通过dexClassloader去做的,有兴趣的自行了解)
2,拿到PathClassLoader 的Element数组,再拿到我们定义的DexClassLoader 的 Element数组,然后把数组合并一下,把我们DexClassLoader 的Element数组放在前边,再把最后的Element数组给PathClassLoader 设置回去,就实现了热修复.
上代码
public class HotFixTest {
public void test() throws NullPointerException {
throw new NullPointerException();
}
}
public class Secondectivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_secondectivity);
findViewById(R.id.click_View2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new HotFixTest().test();
}
});
}
}
这个是需要修复的类,现在我们抛了一个异常,这个代码都能看懂吧 一点击就会抛出空指针异常
public class HotFixTest {
public void test() throws NullPointerException {
Toast.makeText(MyApplication.getContext(),"修复啦 ",Toast.LENGTH_SHORT).show();
}
}
1,这个是我们需要替换的类,修改之后现在把它打成dex文件.可以用javac或者Android 首先编译程.class文件
2,然后把class文件通过dx指令生成dex文件
第一种配置环境变量
第二种直接目录下执行,直接在build-tools/安卓版本 目录下使用命令行窗口使用。
dx --dex --output=输出的dex文件完整路径 (空格) 要打包的完整class文件所在目录
包名一定要跟你定义的包名一致.
dx --dex --output=C:\Users\76209\Desktop\dex\pach.dex C:\Users\76209\Desktop\class
3,转换成dex文件之后,我们需要通过下载啊或者其他方式放到包名下能够加载到的地方
这里用adb命令
adb push C:\Users\76209\Desktop\dex\pach.dex /storage/emulated/0/Android/data/com.example.lenkdlist/files/patch/patch.dex
4,核心代码要来了
public class XHotFixUtil {
public static void fix() throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
//拿到补丁dex的文件路径
File dexSrc = MyApplication.getContext().getExternalFilesDir("patch");
if (!dexSrc.exists()) {
return;
}
//遍历文件夹找出dex,jar,apk结尾的文件
File[] fileList = dexSrc.listFiles();
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < fileList.length; i++) {
File file = fileList[i];
String fileName = file.getName();
if (fileName.endsWith(".dex") || fileName.endsWith(".jar") || fileName.endsWith(".apk")) {
stringBuffer.append(file.getAbsolutePath());
if (i != 0) {
stringBuffer.append(":");
}
//多个dex路径 添加默认分隔符 :
}
}
//创建解压的文件目录,给dexClassloader输出用
File outDex = MyApplication.getContext().getDir("outDex", Context.MODE_PRIVATE);
if (!outDex.exists()){
outDex.mkdirs();
}
String outDexPatch =outDex.getAbsolutePath();
//拿到classLoader对象
ClassLoader patchClassLoader = MyApplication.getContext().getClassLoader();
DexClassLoader dexClassLoader = new DexClassLoader(stringBuffer.toString(),outDexPatch , null, MyApplication.getContext().getClassLoader().getParent());
//拿到他们加载的数组
Object patchElements = getElements(patchClassLoader);
Object dexElements = getElements(dexClassLoader);
//合并数组,并且设置给patchClassLoader对象;
Object merge = merge(patchElements, dexElements);
setElements(patchClassLoader,merge);
}
/**
* 拿到ClassLoader 对象的数组
* @param classLoader
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
* @throws ClassNotFoundException
*/
public static Object getElements(ClassLoader classLoader) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
Class<?> baseClazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathList = baseClazz.getDeclaredField("pathList");
pathList.setAccessible(true);
Object pathListObj = pathList.get(classLoader);
Field dexElements = pathListObj.getClass().getDeclaredField("dexElements");
dexElements.setAccessible(true);
Object o = dexElements.get(pathListObj);
return o;
}
/**
* 合并两个 elements数组
* @param elements1
* @param elements2
* @return
*/
public static Object merge(Object elements1,Object elements2){
Class<?> componentType = elements1.getClass().getComponentType();//拿到数组的泛型
int length1 = Array.getLength(elements1);
int length2 = Array.getLength(elements2);
int total = length1 +length2;
//数组创建不能指定泛型
Object mergeElements = Array.newInstance(componentType, total);
System.arraycopy(elements2, 0, mergeElements, 0,length2);
System.arraycopy(elements1, 0, mergeElements, length2,length1);
return mergeElements;
}
/**
* 设置给默认类的加载器
* @param classLoader
* @param elements
* @throws NoSuchFieldException
* @throws IllegalAccessException
* @throws ClassNotFoundException
*/
public static void setElements(ClassLoader classLoader,Object elements) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
Class<?> baseClazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathList = baseClazz.getDeclaredField("pathList");
pathList.setAccessible(true);
Object pathListObj = pathList.get(classLoader);
Field dexElements = pathListObj.getClass().getDeclaredField("dexElements");
dexElements.setAccessible(true);
dexElements.set(pathListObj,elements);
}
}
面试知识
1,双亲委派机制,注意是包含,并不是继承,充分了解机制
2,利用创建dexClassLoader 对象把新的dex文件加载进去
3,patchList对象的elements数组,利用双亲委派机制进行,数组合并.
4,这只是其中一类热修复的原理,还有底层替换,instance run原理(使用 ASM 在每一个方法 中注入了代码)