Android进阶android 疑难问题android技术

探索Android开源框架 - 11. 热修复原理

2022-01-14  本文已影响0人  今阳说

热修复技术介绍

  1. Hybird:原生+H5混合开发,缺点是人工成本搞,用户体验不如纯原生方案好;
  2. 插件化:移植成本高,对老代码的改造费时费力,而且无法动态修改;
  3. 热修复技术,将补丁上传到云端,app可以直接从云端下来补丁直接应用;
  1. 发布新版本代价较大,用户下载安装成本高;
  2. 版本更新的效率问题,需要较长时间来完成版本覆盖;
  3. 版本更新的升级率问题,不升级版本的用户得不到修复,强更又比较暴力。
  4. 小而重要的功能,需要短时间内完成版本覆盖,比如节日活动。
百家争鸣的热修复框架

热修复技术原理

代码修复:

1. 类加载方案

优点:
缺点
Dex分包
  1. 65536限制:DVM指令集的方法调用指令invoke-kind索引为16bits,最多能引用 65535个方法;
  2. LinearAlloc限制:DVM中的LinearAlloc是一个固定的缓存区,当方法数过多超出了缓存区的大小,安装时提示INSTALL_FAILED_DEXOPT;
ClassLoader
几种不同的实现:
  1. 将补丁包放在Element数组的第一个元素得到优先加载(QQ空间的超级补丁和Nuwa)
  2. 将补丁包中每个dex 对应的Element取出来,之后组成新的Element数组,在运行时通过反射用新的Element数组替换掉现有的Element 数组(饿了么的Amigo);
  3. 将新旧apk做了diff,得到patch.dex,然后将patch.dex与手机中apk的classes.dex做合并,生成新的classes.dex,然后在运行时通过反射将classes.dex放在Element数组的第一个元素(微信Tinker)
  4. Sophix:dex的比较粒度在类的维度,并且 重新编排了包中dex的顺序,classes.dex,classes2.dex..,可以看作是 dex文件级别的类插桩方案,对旧包中的dex顺序进行打破重组

2. 底层替换方案

优点
缺点
几种不同的实现:
  1. 采用替换ArtMethod结构体中的字段,这样会有兼容问题,因为手机厂商的修改 以及 android版本的迭代可能会导致底层ArtMethod结构的差异,导致方法替换失败;(AndFix)
  2. 同时使用类加载和底层替换方案,针对小修改,在底层替换方案限制范 围内,还会再判断所运行的机型是否支持底层替换方案,是就采用底层替换(替换整个ArtMethod结构体,这样不会存在兼容问题),否则使用类加载替换;(Sophix)

3. Instant Run方案

Instant Run新特性的原理就是当进行代码改动之后,会进行增量构建,也就是仅仅构建这部分改变的代码,并将这部分代码以补丁的形式增量地部署到设备上,然后进行代码的热替换,从而观察到代码替换所带来的效果。其实从某种意义上讲,Instant Run和热修复在本质上是一样的。

Instant Run打包逻辑
  1. manifest注入:InstantRun会生成一个自己的application,然后将这个application注册到manifest配置文件里面,这样就可以在其中做一系列准备工作,然后再运行业务代码;
  2. nstant Run代码放入主dex:manifest注入之后,会将Instant Run的代码放入到Android虚拟机第一个加载的dex文件中,包括classes.dex和classes2.dex,这两个dex文件存放的都是Instant Run本身框架的代码,而没有任何业务层的代码。
  3. 工程代码插桩——IncretmentalChange;这个插装里面会涉及到具体的IncretmentalChange类。
  4. 工程代码放入instantrun.zip;这里的逻辑是当整个App运行起来之后才回去解压这个包里面的具体工程代码,运行整个业务逻辑。
//$change实现了IncrementalChange这个抽象接口。
//当点击InstantRun时,如果方法没有变化则$change为null,就调用return,不做任何处理。
//如果方法有变化,就生成替换类,假设MainActivity的onCreate方法做了修改,就会生成替换类MainActivity$override,
//这个类实现了IncrementalChange接口,同时也会生成一个AppPatchesLoaderImpl类,这个类的getPatchedClasses方法
//会返回被修改的类的列表(里面包含了MainActivity),根据列表会将MainActivity的$change设置为MainActivity$override
//因此满足了localIncrementalChange != null,会执行MainActivity$override的access$dispatch方法,
//access$dispatch方法中会根据参数”onCreate.(Landroid/os/Bundle;)V”执行MainActivity$override的onCreate方法,
//从而实现了onCreate方法的修改。
IncrementalChange localIncrementalChange = $change;
if (localIncrementalChange != null) {//2
    localIncrementalChange.access$dispatch(
            "onCreate.(Landroid/os/Bundle;)V", new Object[] { this,
                    paramBundle });
    return;
}
被废弃的Instant Run

Android Studio 3.5 中一个显著变化是引入了 Apply Changes,它取代了旧的 Instant Run。Instant Run 是为了更容易地对应用程序进行小的更改并测试它们,但它会产生一些问题。为了解决这一问题,谷歌已经彻底删除了 Instant Run,并从根本上构建了 Apply Changes ,不再在构建过程中修改 APK,而是使用运行时工具动态地重新定义类,它应该比立刻运行更可靠和更快。

优点
缺点

资源修复:

  1. 创建新的AssetManager,并通过反射调用addAssetPath加载完整的新资源包;
  2. 找到所有之前引用到原有AssetManager的地方,通过反射,把引用处 替换为新AssetManager;

so库修复:

@CallerSensitive
public static void load(String filename) {
    Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
}
@CallerSensitive
public static void loadLibrary(String libname) {
    Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
  1. 判断so文件是否已经加载,若已经加载判断与class_Loader是否一样,避免so重复加载;
  2. 如果so文件没有被加载,打开so并得到so句柄,如果so句柄获取失败,就返回false,常见新的SharedLibrary,如果传入path对应的library为空指针,就将创建的SharedLibrary赋值给library,并将library存储到libraries_中;
  3. 查找JNI_OnLoad的函数指针,根据不同情况设置was_successful的值,最终返回该was_successful;
两种方案:
  1. 将so补丁插入到NativeLibraryElement数组的前部,让so补丁的路径先被返回和加载;
  2. 调用System.load方法来接管so的加载入口;

参考

我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章

上一篇下一篇

猜你喜欢

热点阅读