前言
什么是热修复?
当我们应用上线后出现bug需要及时修复时,不用通过新发安装包,而是通过发布补丁包,在客户无感知的情况下修复bug;
热修复执行流程
开发侧修复bug提供补丁包给服务端,服务端将补丁包下发给客户端,客户端拿到补丁包后执行热修复相关代码进行修复;
常用三方热修复框架原理浅析
常见热修复方案比较
AndFix 阿里巴巴热修复方案【已停止维护】
github地址
AndFix
方案流程
在native动态替换java层的方法,通过native层hook java层的代码,从而改变执行顺序,进行修复;
Robust 美团热修复方案
github地址
Robust
方案流程
对每个函数都在编译打包阶段自动的插入一段代码,类似于代理,将方法执行的代码重定向到其他方法中;
@Modify
public long getIndex() {
return 100;
}
public long getIndex() {
if (changQuickRedirect != null) {
return 修复的实现逻辑;
}
return 100;
}
Tinker 腾讯热修复方案
github地址
Tinker Tinker通过计算比对指定的Base Apk中的dex与修复后的Apk中的dex的区别,补丁包中的内容即为两者差分的描述。运行时将Base Apk中的dex与补丁包进行合成,重启后加载全新合成后的dex文件;
热修复简单实战
在前面我们学习了插件化 相关知识,了解了类加载机制原理 以及实现插件化调用 ,其实热修复 和插件化 实施差不多,区别主要是当进行dex合并 的时候,需要将补丁包中的dex加载到base dex的前面,这样才能达到加载补丁包中class的目的。关于类加载机制原理 这里就不再赘述,有需要可直接参考:Android插件化学习之初识类加载机制
这里我们简单写个热修复相关的demo;
public class TestUtils {
public static void test() {
throw new IllegalArgumentException("故意写bug....");
}
}
- 将bug修复并打包成dex文件存放到sdcard下
public class TestUtils {
public static void test() {
Log.e(TestUtils.class.getSimpleName(), "bug 已修复...");
}
}
使用dx命令打包成dex存放在sdcard目录下;
public class LoadUtils {
public static void loadPluginDexFile(Context context) {
try {
Class<?> baseDexClassLoaderClazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = baseDexClassLoaderClazz.getDeclaredField("pathList");
pathListField.setAccessible(true);
Class<?> dexPathListClazz = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClazz.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
ClassLoader hostClassLoader = context.getClassLoader();
Object pathList = pathListField.get(hostClassLoader);
Object[] hostDexElements = (Object[]) dexElementsField.get(pathList);
DexClassLoader pluginDexClassLoader = new DexClassLoader("/sdcard/patch.dex", context.getCacheDir().getAbsolutePath(), null, hostClassLoader);
Object pluginPathList = pathListField.get(pluginDexClassLoader);
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
Object[] resultDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), hostDexElements.length + pluginDexElements.length);
System.arraycopy(pluginDexElements, 0, resultDexElements, 0, pluginDexElements.length);
System.arraycopy(hostDexElements, 0, resultDexElements, pluginDexElements.length, hostDexElements.length);
dexElementsField.set(pathList, resultDexElements);
} catch (Exception e) {
e.printStackTrace();
Log.e("LoadUtils", e.toString());
}
}
Android N混合编译
ART 是在Android KitKat(Android4.0)进入并在Lolipop(Android 5.0)中设为默认运行环境,可以看作Dalvik 2.0; ART 模式在Android7.0之前安装APK会采用AOT(Ahead of Time:提前编译、静态编译)预编译为机器码; 而在Android N(Android7.0)使用混合模式的运行时,应用在安装时不做编译,而是运行时解释字节码,同时在JIT编译了一些代码后将这些代码信息记录至Profile文件,等到设备空闲的时候使用AOT(All-Of-the-Time compilation:全时段编译)编译生成称为app_image的base.art(类对象映像)文件,这个art文件会在apk启动时自动加载(相当于缓存)。根据类加载原理,类被加载后无法替换,因此存在无法修复的问题;
混合编译热修复解决方案
运行时,通过反射替换系统创建的PathClassLoader App image中的class是插入到PathClassLoader 中的ClassTable 中;假设我们完全废弃掉PathClassLoader ,而是采用新建一个PathClassLoader 加载后续所有的类,即可达到将缓存数据无用化的目的;具体方式可参考Tinker 中的替换方式;
总结
热修复 实现原理归根还是类加载机制 ,与插件化 方案的不同之处在于修复dex 需要放在合并后的dex数组 前面,才能执行修复后的代码;本质上都是对于类加载机制 的运用;
|