• 热修复技术可谓是百花齐放


    1. 简述

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

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

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

    2.优势

    2.1 横向比较

    方案对比SophixTinkerAmigo
    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]支持服务端控制

    说明:

    • [0] 部分情况指的是构造方法、参数数目大于8或者参数包括long,double,float基本类型的方法。
    • [1] 冷启动方式,指的是需要重启app在下次启动时才能生效。
    • [2] 对于Andfix及Hotfix 1.X能够支持的代码变动情况,都能做到即时生效。而对于其他代码变动较大的情况,会走冷启动方式,此时就无法做到即时生效。
    • [3] Hotfix 1.X已经支持绝大部分主流手机,只是在X86设备以及修改了虚拟机底层结构的ROM上不支持。
    • [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的缺陷

    QZoneTinker
    原理为了解决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的解决方案

    • Dalvik下采用阿里自研的全量dex方案:不是考虑把补丁包的dex插到所有dex前面(dex插桩),而是想办法在原理的dex中删除(只是删除了类的定义)补丁dex中存在的类,这样让系统查找类的时候在原来的dex中找不到,那么只有补丁中的dex加载到系统中,系统自然就会从补丁包中找到对应的类。
    • Art下本质上虚拟机以及支持多dex的加载,Sophix的做法仅仅是把补丁dex作为主dex(classes.dex)而已,相当于重新组织了所有的dex文件:把补丁包的dex改名为classes.dex,以前apk的所有dex依次改为classes2.dex、classes3.dex … classesx.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: (检查到稳健接入初始化错误)-> 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"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    结尾

    谢谢大家看完,如有不恰当、不充分的地方,欢迎大家指正。针对这块知识点我在学习过程中,进行了详细的整理梳理了一些学习笔记,有需要参考学习的小伙伴可以 点击这里查看获取方式 传送门直达 !!!



  • 相关阅读:
    三年的功能测试,让我女朋友跑了,太难受了...
    一、利用固件库模板点灯(附模板及案例程序)
    过滤器(Filter)
    Spring面试题2:说一说IOC控制反转以及底层是如何实现的?说一说面向切面的编程(AOP)以及底层是如何实现的?
    关于原型交互设计文档的一些建议
    flutter系列之:flutter架构什么的,看完这篇文章就全懂了
    MODBUS-TCP转MODBUS-RTU通信应用(S7-1200和串口服务器通信)
    Kafka之Producer网络传输
    Abnova 鸡抗小鼠 IgG (H&L) 二抗(过氧化物酶)说明书
    数商云:跨境美妆步入下半场,跨境电商商城系统助力企业打好“出海”攻坚战
  • 原文地址:https://blog.csdn.net/weixin_61845324/article/details/126090523