Android开发程序员Android 技术文章

Android的热更新方案之Sophix非侵入式热修复方案

2020-07-04  本文已影响0人  Android进阶架构

1. 简述

热修复技术可谓是百花齐放,微信的Tinker、QQ空间的Nuwa、饿了么的Amigo、美团的Robust等等,各个热修复方案争相发布,都声称自己可以做到全方位全功能的热修复。不过他们各自有自身的局限性,或者不够稳定,或者补丁过大,或者效率低下,或者使用起来过于繁琐,大部分技术上看起来似乎可行,但实际体验并不好。

在2017年6月11日,手淘技术团队联合阿里云正式发布了Android移动热修复方案——Sophix。Sophix的核心设计理念,就是非侵入性。Sophix这个名字,是来源于Sophic(明智的)+ FIX,一个更明智的热修复方案。

Sophix的横空出世,将会打破各家热修复技术纷争的局面。我们可以满怀信心地说,在Android热修复的三大领域:代码修复、资源修复、so修复方面,以及方案的安全性和易用性方面,Sophix都做到了业界领先。

2.优势

2.1 横向比较

方案对比 Sophix Tinker Amigo
DEX修复 同时支持即时生效和冷启动修复 冷启动修复 冷启动修复
资源更新 差量包,不用合成 差量包,需要合成 全量包,不用合成
SO库更新 插桩实现,开发透明 替换接口,开发不透明 插桩实现,开发透明
性能损耗 低,仅冷启动情况下有些损耗 高,有合成操作 低,全量替换
四大组件 不能新增 不能新增 能新增
生成补丁 直接选择已经编好的新旧包在本地生成 编译新包时设置基线包 上传完整新包到服务端
补丁大小
接入成本 傻瓜式接入 复杂 一般
Android版本 全部支持 全部支持 全部支持
安全机制 加密传输及签名校验 加密传输及签名校验 加密传输及签名校验
服务端支持 支持服务端控制 支持服务端控制 支持服务端控制

可以看到,Sophix在各个指标上全面占优。而其中唯一支持不完善的地方就是四大组件,四大组件可以修改代码,但是无法做到新增。这是因为如果要新增四大组件,必须在AndroidManifest里面预先插入代理组件,并且尽可能声明所有权限,而这么做就会给原先的app添加很多臃肿的代码,对app运行流程的侵入性很强,所以,本着对开发者透明与代码极简的原则,它没有做这种多余的处理。

2.2 纵向比较

方案对比 Andfix开源版本 阿里Hotfix 1.X 阿里Hotfix最新版 (Sophix)
方法替换 支持,除部分情况[0] 支持,除部分情况 全部支持
方法增加减少 不支持 不支持 以冷启动方式支持[1]
方法反射调用 只支持静态方法 只支持静态方法 以冷启动方式支持
即时生效 支持 支持 视情况支持[2]
多DEX 不支持 支持 支持
资源更新 不支持 不支持 支持
so库更新 不支持 不支持 支持
Android版本 支持2.3~7.0 支持2.3~6.0 全部支持包含7.0以上
已有机型 大部分支持[3] 大部分支持 全部支持
安全机制 加密传输及签名校验 加密传输及签名校验
性能损耗 低,几乎无损耗 低,几乎无损耗 低,仅冷启动情况下有些损耗
生成补丁 繁琐,命令行操作 繁琐,命令行操作 便捷,图形化界面
补丁大小 不大,仅变动的类 小,仅变动的方法 不大,仅变动的资源和代码[4]
服务端支持 支持服务端控制[5] 支持服务端控制

说明:

3. 技术突破

3.1 原理(双剑合璧)

image

3.2 优化Andfix(突破底层结构差异,解决稳定性问题)

Andfix底层ArtMethod结构时采用内部变量一一替换,倒是这个各个厂商是会修改的,所以兼容性不好。

Sophix改变了一下思路,采用整体替换方法结构,忽略底层实现,从而解决兼容稳定性问题。

这么一来,不仅解决了兼容性问题,并且由于忽略了底层ArtMethod结构的差异,对于所有的Android版本都不再需要区分,代码量大大减少。即使以后的Android版本不断修改ArtMethod的成员,只要保证ArtMethod数组仍是以线性结构排列,就能直接适用于将来的Android 9.0、9.1等新版本,无需再针对新的系统版本进行适配了。事实也证明确实如此,当拿到Google刚发不久的Android P(9.0)开发者预览版的系统时,hotfix demo直接就能顺利地加载补丁跑起来了,我们并没有做任何适配工作,鲁棒性极好。

3.3 突破QQ和Tinker的缺陷

QZone Tinker
原理 为了解决Dalvik下unexpected dex problem异常而采用插桩的方式,单独放一个帮助类在独立的dex中让其他类调用,阻止了类被打上CLASS_ISPREVERIFIED标志从而规避问题的出现。最后加载补丁dex得到dexFile对象作为参数构建一个Element对象插入到dex-Elements数组的最前面。 提供dex差量包,整体替换dex的方案。差量的方式给出patch.dex,然后将path.dex与应用的classes.dex合并成一个完整的dex,完整dex加载得到的dexFile对象作为菜蔬构建一个Element对象然后整体替换掉旧的dex-Elements数组。
优点 没有合成整包,patch比较小,比较灵活 自研dex差异算法,path包很小,dex merge成完整的dex,Dalvik不影响类加载性能,Art下也不存在必须包含父类/引用类的情况
缺点 Dalvik下影响类的加载性能,Art下类地址写死导致必须包含父类/引用,最后patch包很大 Dex合并内存消耗在VM heap上,容易OOM,最终导致dex合并失败

Sophix对dex的解决方案

dex的merge图解:


3.3 资源修复另辟蹊径

常用方案(Instant Run技术):这种方案的兼容问题在于替换AssetManager的地方

目前市面上的很多资源热修复方案基本上都是参考了Instant Run的实现。实际上,Instant Run的推出正是推动这次热修复浪潮的主因,各家热修复方案,在代码、资源等方面的实现,很大程度上地参考了Instant Run的代码,而资源修复方案正是被拿来用到最多的地方。

简要说来,Instant Run中的资源热修复分为两步:

  1. 构造一个新的AssetManager,并通过反射调用addAssetPath,把这个完整的新资源包加入到AssetManager中。这样就得到了一个含有所有新资源的AssetManager。
  2. 找到所有之前引用到原有AssetManager的地方,通过反射,把引用处替换为AssetManager。

其实大量代码都是在处理兼容性问题和找到所有AssetManager的引用处,真正的替换的逻辑其实很简单。

Sophix资源修复方案

Sophix方案没有直接使用Instant Run的技术,而是另辟蹊径,构造了一个package id为0x66的资源包,这个包里只包含改变了的资源项,然后直接在原有AssetManager中addAssetPath这个包就可以了。由于补丁包的package id为0x66,不与目前已经加载的0x7f冲突,因此直接加入到已有的AssetManager中就可以直接使用了。补丁包里面的资源,只包含原有包里面没有而新的包里面有的新增资源,以及原有内容发生了改变的资源。并且,我们采用了更加优雅的替换方式,直接在原有的AssetManager对象上进行析构和重构,这样所有原先对AssetManager对象的引用是没有发生改变的,所以就不需要像Instant Run那样进行繁琐的修改了。

可以说,Sophix的资源修复方案,优越性超过了Google官方的Instant Run方案。整个资源替换的方案优势在于:

  1. 不修改AssetManager的引用处,替换更快更完全。(对比Instanat Run以及所有copycat的实现)
  2. 不必下发完整包,补丁包中只包含有变动的资源。(对比Instanat Run、Amigo等方式的实现)
  3. 不需要在运行时合成完整包。不占用运行时计算和内存资源。(对比Tinker的实现)

所以,我们不要被所谓的“官方实现”束缚住手脚,其实Instant Run的开发团队和Android framework的开发团队并不是同一个团队,他们对于Android系统机制的理解未必十分深入。

3.4 SO修复另辟蹊径

so库的修复本质上是对native方法的修复和替换。

我们知道JNI编程中,native方法可以通过动态注册和静态注册两种方式进行。动态注册的native方法必须实现JNI_OnLoad方法,同时实现一个JNINativeMethod[]数组,静态注册的native方法必须是Java+类完整路径+方法名的格式。

动态注册的native方法映射通过加载so库过程中调用JNI_OnLoad方法调用完成,静态注册的native方法映射是在该native方法第一次执行的时候才完成映射,当然前提是该so库已经load过。

sophix采用的是类似类修复反射注入方式。把补丁so库的路径插入到nativeLibraryDirectories数组的最前面,就能够达到加载so库的时候是补丁so库,而不是原来so库的目录,从而达到修复的目的。

采用这种方案,完全由Sophix在启动期间反射注入patch中的so库。对开发者依然是透明的。不用像某些其他方案需要手动替换系统的System.load来实现替换目的。

4. 项目集成

移动热修复(Mobile Hotfix),阿里云有详细的接入文档,按照文档一步一步来,很容易就实现了。

5. 集成问题

5.1 检查到稳健接入初始化错误

":checkApplication FAILED\r"
"BUILD FAILED\r"
"java.lang.RuntimeException: (检查到稳健接入初始化错误<init error 501>)-> Sophix Stub Application类中不得使用非Android SDK的类: [Lap;]\r"
"\tat com.taobao.sophix.c.j.a(PreCheckApplication.java:110)\r"
"\tat com.taobao.sophix.c.e.a(PatchCommand.java:247)\r"
"\tat com.taobao.sophix.Main.main(Main.java:34)\r"

解决方法:
SophixPatchTool工具的打开高级设置->不勾选初始化检查

结尾

谢谢大家看完,如有不恰当、不充分的地方,欢迎大家指正。

最后在这里我分享自己收录在整理的Android学习知识点文档,里面有热修复相关面试题和其他知识点的讲解,希望可以帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,可以分享给身边好友一起学习
有需要的朋友可以点赞+评论+转发,关注我,然后简信我【666】获取,也可以点击《Android学习PDF+架构视频+面试文档》查看更多;

上一篇下一篇

猜你喜欢

热点阅读