Android Classloader热修复

惯例段子。

Android Classloader热修复_第1张图片
哈哈哈嗝
阅读本文你可以掌握,热修复的原理和简单实现.
目录
Classloader热修复原理
热修复代码实现
面试知识

Classloader热修复原理

Android Classloader热修复_第2张图片
classloaderDemo

从这个图上能看出什么?

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文件所在目录


Android Classloader热修复_第3张图片
class.png

包名一定要跟你定义的包名一致.
dx --dex --output=C:\Users\76209\Desktop\dex\pach.dex C:\Users\76209\Desktop\class


dx.png

3,转换成dex文件之后,我们需要通过下载啊或者其他方式放到包名下能够加载到的地方
这里用adb命令

adb push C:\Users\76209\Desktop\dex\pach.dex /storage/emulated/0/Android/data/com.example.lenkdlist/files/patch/patch.dex

adb.png

然后可以从包名下看到dex文件
Android Classloader热修复_第4张图片
手机.png

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 在每一个方法 中注入了代码)

你可能感兴趣的:(Android Classloader热修复)