热更新原理总结

Instant Run概述

Instant Run 是 Android Studio2.0 之后新增的一个运行机制,能够减少开发人员第二次及以后的构建时间。

在没有Instant run之前,编译部署应用程序的流程如下图:

在这里插入图片描述
传统的编译部署,需要重新安装APP和重启 APP。这样会很耗时。

使用Instant Run会避免这一情况。

Instant Run基于更改的部分进行构建和部署。

在这里插入图片描述
Instant Run部署有三种方式,Instant Run会根据代码的情况来决定采用哪种部署方式。

  • hot swap——修改一个现有方法中的代码时会采用hot swap,他不需要重启当前Activity即可实现使更改的代码运行
  • warm swap——修改现有资源文件时采用warm swap,他需要重启Activity。
  • cold swap——当增删改一个字段、方法或类时会采用cold swap。他需要重启APP。

Gradle Transform

Gradle Transform是Android官方提供给开发者在项目构建阶段即由class到dex转换期间修改class文件的一套api。

Instant Run修复原理

  • 当第一次构建APP时,Instant Run利用Transform API在每一个类中注入一个叫change的字段
  • change实现了incrementalChange接口。
  • 在每一个方法中添加一个逻辑判断,如果change不为空就执行change的accessdispatch方法,否则执行原方法的逻辑。
  • 当我们修改完对应的代码点击run后,Instant Run会生成对应的patch文件。
  • patch文件中补丁类的名字是修改类的名字+$override,这个类实现了IncrementalChange接口
  • 同时生成一个AppPatchsLoaderImpl类,这个类维护着一个所有修改过的类的列表
  • 通过AppPatchsLoaderImpl将修改过的类中的change字段赋值为生成的xxx$override。

这样就实现了对代码的修改。

ClassLoader 修复代码

类加载方案基于Dex分包方案。

65535限制

Android系统有一个65536限制,意思是应用中引用的方法数不能超过65536个。

产生一个限制的原因是Dalvik Bytecode的限制,Dalvik指令集的方法调用指令invoke-kind索引为16bite,最多应用65536个方法。

LinearAlloc限制

Dalvik中的LinearAlloc是一个固定的缓冲区,当方法数超出了缓存区的大小时会报错。

针对这两个限制,产生了Dex分包方案。Dex分包方案主要做的是在打包时将应用代码分成多个Dex。

将应用启动时必须用到的类和这些类的引用类放到主Dex,其他代码放到次Dex,当应用启动时先加载主Dex,等到启动后再动态加载次Dex。

我们都知道,Android的类加载主要有两个:PathClassLoader和DexClassLoader,他们都是BaseDexClassLoader 的子类。

其中PathClassLoader在应用启动时创建,用于加载apk的dex文件。

DexClassLoader可以加载SD卡上包含dex的.jar和.apk文件。

BaseDexClassLoader 有个字段private final DexPathList pathList,BaseDexClassLoader 的findClass()、findResource()均是基于pathList实现的。

DexPathList 里面有一个Element数组,Element内部封装了DexFile ,DexFile用于加载dex文件,所以每个dex文件对应一个Element。

当通过BaseDexClassLoader 查找一个类时,会通过以下路线进行查找:

  • 调用 BaseDexClassLader 的 findClass(String name)方法
  • 接着交给 DexPathList 的findClass()方法
  • 然后遍历Element数组,调用Element的findClass()方法
  • Element的findClass()方法内部调用DexFile的loadClassBinaryName方法查找类。

当查找到了就直接返回,没有找到就去下一个Element查找。

所以,根据上述的查找流程,我们只需将我们修复后的类打包成Dex文件,存放到Element数组的最前面,这样就会优先从修复后的dex中查找并返回

在这里插入图片描述
具体操作的步骤就是

  • 获得APP的PathClassLoader
  • 通过dex包路径和父加载器创建我们的DexClassLoader
  • 反射得到两个加载器的pathList
  • 反射得到两个pathList的dexElements
  • 创建新的dexElements,将我们的Element放入dexElements最前面
  • 反射赋值给PathClassLoader

代码

由插件化那里搬过来的,忽略插件字眼~

	DexClassLoader dexClassLoader = new DexClassLoader(Environment.getExternalStorageDirectory().getAbsolutePath() + "/classes.dex",
		getCacheDir().getAbsolutePath(), null, getClassLoader());*/

    private void fix() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        //获取PathClassLoader
        PathClassLoader pathClassLoader =
                (PathClassLoader) getApplicationContext().getClassLoader();

        //分别获取宿主和插件的PathList
        Object suzhuPathList = getPathList(pathClassLoader);
        Object chajianPathList = getPathList(dexClassLoader);

        //获得PathList的element并合并
        Object newElements = mergeElements(getElements(suzhuPathList),
                getElements(chajianPathList));
        Log.d("Lpp", "newElements: " + Array.getLength(newElements));

        //重新设置给宿主的dexElement
        Field field = suzhuPathList.getClass().getDeclaredField("dexElements");
        field.setAccessible(true);
        field.set(suzhuPathList, newElements);
    }

    // 获取DexPathList 中的dexElements
    private static Object getElements(Object suzhuPathList) throws NoSuchFieldException,
            IllegalAccessException {
        Class cl = suzhuPathList.getClass();
        Field field = cl.getDeclaredField("dexElements");
        field.setAccessible(true);
        return field.get(suzhuPathList);
    }

    private static Object mergeElements(Object elements2, Object elements1) {
        Log.d("Lpp", "suzhuPathList: " + Array.getLength(elements1));
        Log.d("Lpp", "chajianPathList: " + Array.getLength(elements2));
        int len1 = Array.getLength(elements1);
        int len2 = Array.getLength(elements2);
        Object newArr = Array.newInstance(elements1.getClass().getComponentType(), len1 + len2);
        for (int i = 0; i < len1; i++) {
            Array.set(newArr, i, Array.get(elements1, i));
        }
        for (int i = len1; i < len1 + len2; i++) {
            Array.set(newArr, i, Array.get(elements2, i - len1));
        }
        return newArr;
    }

    // 获取DexPathList
    private static Object getPathList(Object loader) throws ClassNotFoundException,
            NoSuchFieldException, IllegalAccessException {
        Class cl = Class.forName("dalvik.system.BaseDexClassLoader");
        Field field = cl.getDeclaredField("pathList");
        field.setAccessible(true);
        return field.get(loader);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章