Android Classloader热修复

惯例段子。

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

Classloader热修复原理

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


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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容