阿里巴巴对Android热修复技术已经进行了长达多年的探索。git
最开始,是手淘基于Xposed进行了改进,产生了针对Android Dalvik虚拟机运行时的Java Method Hook技术,Dexposed。但这个方案因为对底层Dalvik结构过于依赖,最终没法继续兼容Android5.0之后ART虚拟机,所以做罢。github
后来支付宝提出了新的热修复方案Andfix。Andfix一样是一种底层结构替换的方案,也达到了运行时生效即时修复的效果,而且重要的是,作到了Dalvik和ART环境的全版本兼容。阿里百川结合手淘在实际工程中使用Andfix的经验,对相关业务逻辑解耦后,推出了阿里百川Hotfix方案,并获得了良好的反响。编程
此时的百川Hotfix已是一个很不错的产品了,对于基本的代码修复需求均可以解决,安全性和易用性都作的比较好。然而,它所依赖的基石,Andfix自己,是有局限性的。且不说其底层固定结构的替换方案稳定性很差,其使用范围也存在着诸多限制,虽然能够经过改造代码绕过限制来达到相同的修复目的,但这种方式既不优雅也不方便。而更大的问题是,Andfix只提供了代码层面的修复,对于资源和so的修复都还未能实现。数组
再看一下同期的其余热修复方案,此时的热修复技术可谓是百花齐放,微信的Tinker、QQ空间的Nuwa、饿了么的Amigo、美团的Robust等等,各个热修复方案争相发布,都声称本身能够作到全方位全功能的热修复。不过他们各自有自身的局限性,或者不够稳定,或者补丁过大,或者效率低下,或者使用起来过于繁琐,大部分技术上看起来彷佛可行,但实际体验并很差。而在咱们看来,有不少技术细节可以作得更加完美。安全
终于在2017年6月11日,手淘技术团队联合阿里云正式发布了新一代Android移动热修复方案——Sophix。微信
Sophix的横空出世,将会打破各家热修复技术纷争的局面。咱们能够满怀信心地说,在Android热修复的三大领域:代码修复、资源修复、so修复方面,以及方案的安全性和易用性方面,Sophix都作到了业界领先。app
Sophix的核心设计理念,就是非侵入性。框架
咱们的打包过程不会侵入到apk的build流程中。咱们所须要的,只有已经生成完毕的新旧apk,而至于apk是如何生成的——是Android Studio打包出来的、仍是Eclipse打包出来的、或者是自定义的打包流程,咱们一概不关心。在生成补丁的过程当中间既不会改变任何打包组件,也不插入任何AOP代码,咱们极力作到了——不添加任何超出开发者预期的代码,以免多余的热修复代码给开发者带来困扰。函数
在Sophix中,惟一须要的就是初始化和请求补丁两行代码,甚至连入口Application类咱们都不作任何修改,这样就给了开发者最大的透明度和自由度。咱们甚至从新开发了打包工具,使得补丁工具操做图形界面化,这种所见即所得的补丁生成方式也是阿里热修复独家的。所以,Sophix的接入成本也是目前市面上全部方案里最低的。工具
这种非侵入式热更新理念,是咱们在设计过程当中从用户使用角度进行了深刻思考而提炼出的核心思想。
这里的用户,指的天然是广大的开发者。对于开发者而言,热修复应该是一个与业务无关的SDK组件,在整个开发过程当中感知不到它的存在。最理想的状况,就是开发者拿过来两个apk,一个是已经安装在手机上的apk,另外一个是将要发布出去的apk。咱们直接经过工具,就能够根据这两个apk生成补丁,而后把这个补丁下发给已经安装的旧app上,就能够直接加载,使旧app重生为新的app。而这个加载了补丁包新app,在功能和使用上,将会和直接安装新apk别无二致。
至于Sophix这个名字,是来源于Sophic(明智的)+ FIX,一个更明智的热修复方案。
下面的这张表格,从几个热修复最重要的维度,把Sophix和另外两个主要商业化热修复方案进行了比较。
方案对比 | Sophix | Tinker | Amigo |
---|---|---|---|
DEX修复 | 同时支持即时生效和冷启动修复 | 冷启动修复 | 冷启动修复 |
资源更新 | 差量包,不用合成 | 差量包,须要合成 | 全量包,不用合成 |
SO库更新 | 插桩实现,开发透明 | 替换接口,开发不透明 | 插桩实现,开发透明 |
性能损耗 | 低,仅冷启动状况下有些损耗 | 高,有合成操做 | 低,全量替换 |
四大组件 | 不能新增 | 不能新增 | 能新增 |
生成补丁 | 直接选择已经编好的新旧包在本地生成 | 编译新包时设置基线包 | 上传完整新包到服务端 |
补丁大小 | 小 | 小 | 大 |
接入成本 | 傻瓜式接入 | 复杂 | 通常 |
Android版本 | 所有支持 | 所有支持 | 所有支持 |
安全机制 | 加密传输及签名校验 | 加密传输及签名校验 | 加密传输及签名校验 |
服务端支持 | 支持服务端控制 | 支持服务端控制 | 支持服务端控制 |
能够看到,Sophix在各个指标上全面占优。而其中惟一支持不完善的地方就是四大组件,四大组件能够修改代码,可是没法作到新增。这是由于若是要新增四大组件,必须在AndroidManifest里面预先插入代理组件,而且尽量声明全部权限,而这么作就会给原先的app添加不少臃肿的代码,对app运行流程的侵入性很强,因此,本着对开发者透明与代码极简的原则,咱们没有作这种多余的处理。
直接看表格的话,其中有些技术细节可能还看不太明朗,那么接下来,我将从各个角度,深度解读Sophix的技术优点以及它与同类技术的差异。
Sophix的诞生,起初是对原先的阿里百川Hotfix 1.X版本进行升级衍进。
原先百川Hotfix服务端的整套请求控制流程,以及安全检查这部分,是与热修复功能相对分离的,所以咱们依旧保留了这部分的逻辑。
而本来的热修复方案,主要限制在于Andfix自己,咱们最开始也是从突破原先修复限制入手,但愿可以基于原先的Andfix代码作一些必要的改进。然而最终发现,Andfix自身限制几乎是没法绕过的,在运行时对原有类的结构是已经固化在内存中的,它的一些动态属性和很难进行扩展。而且因为Android系统的碎片化,厂商的虚拟机底层结构都不是肯定的,所以直接基于原先机制进行扩展的风险很大。
因此咱们绕开了具体的技术实现细节,直接从修复的原理入手,对原先的代码修复技术进行深挖和改良。
回顾为期九个多月的探索与开发,这其中无处不体现着咱们对易用性和优雅性的极致追求,在技术先进性与易用性上咱们达到了完美的平衡。因此,当咱们再回头看目前市面上的其余热修复技术,真的有一种“曾经沧海难为水”的感受。
代码修复有两大主要方案,一种是阿里系的底层替换方案,另外一种是腾讯系的类加载方案。
这两类方案各有优劣:
底层替换方案是在已经加载了的类中直接替换掉原有方法,是在原来类的基础上进行修改的。于是没法实现对与原有类进行方法和字段的增减,由于这样将破坏原有类的结构。
一旦补丁类中出现了方法的增长和减小,就会致使这个类以及整个Dex的方法数的变化。方法数的变化伴随着方法索引的变化,这样在访问方法时就没法正常地索引到正确的方法了。若是字段发生了增长和减小,和方法变化的状况同样,全部字段的索引都会发生变化。而且更严重的问题是,若是在程序运行中间某个类忽然增长了一个字段,那么对于原先已经产生的这个类的实例,它们仍是原来的结构,这是没法改变的。而新方法使用到这些老的实例对象时,访问新增字段就会产生不可预期的结果。
这是这类方案的固有限制,而底层替换方案最为人诟病的地方,在于底层替换的不稳定性。
传统的底层替换方式,不管是Dexposed、Andfix或者其余安全界的Hook方案,都是直接依赖修改虚拟机方法实体的具体字段。例如,改Dalvik方法的jni函数指针、改类或方法的访问权限等等。这样就带来一个很严重的问题,因为Android是开源的,各个手机厂商均可以对代码进行改造,而Andfix里ArtMethod的结构是根据公开的Android源码中的结构写死的。若是某个厂商对这个ArtMethod结构体进行了修改,就和原先开源代码里的结构不一致,那么在这个修改过了的设备上,通用性的替换机制就会出问题。这即是不稳定的根源。
而咱们也对代码的底层替换原理从新进行了深刻思考,从克服其限制和兼容性入手,以一种更加优雅的替换思路,实现了即时生效的代码热修复。咱们实现的是一种无视底层具体结构的替换方式,
也就是把原先这样的逐一替换
变成了这样的总体替换
这么一来,咱们不只解决了兼容性问题,而且因为忽略了底层ArtMethod结构的差别,对于全部的Android版本都再也不须要区分,代码量大大减小。即便之后的Android版本不断修改ArtMethod的成员,只要保证ArtMethod数组还是以线性结构排列,就能直接适用于未来的Android 8.0、9.0等新版本,无需再针对新的系统版本进行适配了。事实也证实确实如此,当咱们拿到Google刚发不久的Android O(8.0)开发者预览版的系统时,hotfix demo直接就能顺利地加载补丁跑起来了,咱们并无作任何适配工做,鲁棒性极好。
具体技术细节,能够看这篇文章:Android热修复升级探索——追寻极致的代码热替换
类加载方案的原理是在app从新启动后让Classloader去加载新的类。由于在app运行到一半的时候,全部须要发生变动的类已经被加载过了,在Android上是没法对一个类进行卸载的。若是不重启,原来的类还在虚拟机中,就没法加载新类。所以,只有在下次重启的时候,在还没走到业务逻辑以前抢先加载补丁中的新类,这样后续访问这个类时,就会Resolve为新类。从而达到热修复的目的。
再来看看腾讯系三大类加载方案的实现原理。QQ空间方案会侵入打包流程,而且为了hack添加一些无用的信息,实现起来很不优雅。而QFix的方案,须要获取底层虚拟机的函数,不够稳定可靠,而且有个比较大的问题是没法新增public函数。
微信的Tinker方案是完整的全量dex加载,而且可谓是将补丁合成作到了极致,然而咱们发现,精密的武器并不是适用于全部战场。Tinker的合成方案,是从dex的方法和指令维度进行全量合成,整个过程都是本身研发的。虽然能够很大地节省空间,但因为对dex内容的比较粒度过细,实现较为复杂,性能消耗比较严重。实际上,dex的大小占整个apk的比例是比较低的,一个app里面的dex文件大小并非主要部分,而占空间大的主要仍是资源文件。所以,Tinker方案的时空代价转换的性价比不高。
其实,dex比较的最佳粒度,应该是在类的维度。它既不像方法和指令维度那样的细微,也不像bsbiff比较那般的粗糙。在类的维度,能够达到时间和空间平衡的最佳效果。基于这个准则,咱们另辟蹊径,实现了一种彻底不一样的全量dex替换方案。
咱们采用的也是全量合成dex的技术,这个技术是从手淘插件化框架Atlas汲取的。咱们会直接利用Android原先的类查找和合成机制,快速合成新的全量dex。这么一来,咱们既不须要处理合成时方法数超过的状况,对于dex的结构也不用进行破坏性重构。
从图中能够看到,咱们从新编排了包中dex的顺序。这样,在虚拟机查找类的时候,会优先找到classes.dex中的类,而后才是classes2.dex、classes3.dex,也能够看作是dex文件级别的类插桩方案。这个方式十分巧妙,它对旧包与补丁包中classes.dex的顺序进行了打破与重组,最终使得系统能够天然地识别到这个顺序,以实现类覆盖的目的。这将会大大减小合成补丁的开销。
既然底层替换方案和类加载方案各有其优势,把他们联合起来不是最好的选择吗?Sophix的代码修复体系正是同时涵盖了这两种方案。两种方案的结合,能够实现优点互补,彻底兼顾的做用,能够灵活地根据实际状况自动切换。
这两种方案咱们都进行了重大的改进,而且从补丁生成到应用的各个环节都进行了研究,使得两者能很好地整合在一块儿。在补丁生成阶段,补丁工具会根据实际代码变更状况进行自动选择,针对小修改,在底层替换方案限制范围内的,就直接采用底层替换修复吗,这样能够作到代码修复即时生效。而对于代码修改超出底层替换限制的,会使用类加载替换,这样虽然及时性没那么好,但总归能够达到热修复的目的。
另外,运行时阶段,Sophix还会再判断所运行的机型是否支持热修复,这样即便补丁支持热修复,但因为机型底层虚拟机构造不支持,仍是会走类加载修复,从而达到最好的兼容性。
目前市面上的不少资源热修复方案基本上都是参考了Instant Run的实现。实际上,Instant Run的推出正是推进此次热修复浪潮的主因,各家热修复方案,在代码、资源等方面的实现,很大程度上地参考了Instant Run的代码,而资源修复方案正是被拿来用到最多的地方。
简要说来,Instant Run中的资源热修复分为两步:
咱们发现,其实大量代码都是在处理兼容性问题和找到全部AssetManager的引用处,真正的替换的逻辑其实很简单。
咱们的方案没有直接使用Instant Run的技术,而是另辟蹊径,构造了一个package id为0x66的资源包,这个包里只包含改变了的资源项,而后直接在原有AssetManager中addAssetPath这个包就能够了。因为补丁包的package id为0x66,不与目前已经加载的0x7f冲突,所以直接加入到已有的AssetManager中就能够直接使用了。补丁包里面的资源,只包含原有包里面没有而新的包里面有的新增资源,以及原有内容发生了改变的资源。而且,咱们采用了更加优雅的替换方式,直接在原有的AssetManager对象上进行析构和重构,这样全部原先对AssetManager对象的引用是没有发生改变的,因此就不须要像Instant Run那样进行繁琐的修改了。
能够说,咱们的资源修复方案,优越性超过了Google官方的Instant Run方案。整个资源替换的方案优点在于:
因此,咱们不要被所谓的“官方实现”束缚住手脚,其实Instant Run的开发团队和Android framework的开发团队并非同一个团队,他们对于Android系统机制的理解未必十分深刻。只要你认真研读系统代码,实现一个比官方更好的方案绝非难事。因此我想说的是,要想实现技术方案的突破,首先就须要破除所谓“权威”的观念。
资源修复的更多技术细节,可经过这篇文章一探究竟:Android热修复升级探索——资源更新之新思路
so库的修复本质上是对native方法的修复和替换。
咱们知道JNI编程中,native方法能够经过动态注册和静态注册两种方式进行。动态注册的native方法必须实现JNI_OnLoad
方法,同时实现一个JNINativeMethod[]
数组,静态注册的native方法必须是Java+类完整路径+方法名
的格式。
动态注册的native方法映射经过加载so库过程当中调用JNI_OnLoad方法调用完成,静态注册的native方法映射是在该native方法第一次执行的时候才完成映射,固然前提是该so库已经load过。
咱们采用的是相似类修复反射注入方式。把补丁so库的路径插入到nativeLibraryDirectories数组的最前面,就可以达到加载so库的时候是补丁so库,而不是原来so库的目录,从而达到修复的目的。
采用这种方案,彻底由Sophix在启动期间反射注入patch中的so库。对开发者依然是透明的。不用像某些其余方案须要手动替换系统的System.load来实现替换目的。
热修复是一个与业务彻底无关的模块,开发者若是要本身实现一套可靠的热修复框架,将花费大量时间和精力。虽然市面上已经有不少开源的热修复实现,然而其中的不少坑,每每要踩过才知道,等你把这些坑一一踩过以后,可能大量的用户已经对你失去信心。因此,依靠一个稳定可靠、并且简单实用的商业版本,反而能使各方面的成本降到最低。而且,热修复并非简单的客户端SDK,它还包含了安全机制和服务端的控制逻辑,这整条链路也不是短期内能够快速完成的。
仍是那句老话,专业是事交给专业的人去作。开发者应该把更多时间精力放到本身的核心业务之中。
Sophix提供了一套更加完美的客户端服务端一体的热更新方案。作到了图形界面一键打包、加密传输、签名校验和服务端控制发布与灰度功能,让你用最少的时间实现最强大可靠的全方位热更新。而且在代码修复、资源修复、SO库修复方面,都作到了业界最佳。
不少人会把热修复技术跟其余国内厂商的“黑科技”混为一谈。有人说,大家国内开发者就是瞎搞,就不能给咱们Android用户一个更加纯净的环境吗?
这里我须要澄清一下。热修复技术不一样于其余国内的Android“黑科技”。就好比,国内Android进程保活,是让app持续驻留在后台避免被系统杀死,这既耗费手机电量又占内存,浪费了不少手机资源。再好比,app自行定制的推送服务,无节操地对用户进行信息轰炸。还有更过度的全家桶,一个app同时拉起一票app,而且长期占着内存,使得手机卡顿不堪。总归,这些技术都是为了app厂商的利益而损害手机使用者的实际体验。
而热修复技术是彻底不一样的,它达到的是一个手机用户和开发者共赢的目的。不只厂商能够快速迭代更新app,使得功能能最快上线。而且因为热更新过程是毫无感知的,手机用户也减小了繁琐的更新步骤,节省了大量等待更新的时间。这其实是改善了Android的生态环境。只是这其中最重要的,是要保证热修复功能的稳定性。而Sophix的稳定性,是通过了无数开发者检验的,而且还有手淘多年深厚的技术沉淀做为保障。
前段时间,苹果封杀了iOS的热修复功能,不少人就所以不看好热修复技术了。这里我想说的是,苹果的政策并不能证实他有多先进,相反,做为独裁者,苹果作过不少不得人心的事,就好比前段时间封杀微信的文章打赏功能。热修复功能被禁止,会使得不少app不得不靠直接发版进行更新,这样一旦新版本出了问题,整个更新迭代过程变得十分漫长。而且一些试验性功能没法进行灰度,这就使得一个重要功能的更新将直接全量发版,若是功能不够稳定,波及范围就变得很是广。并且,用户须要从新下载整个app,不只流程漫长,本来不到1MB的补丁就能解决的事,如今不得不下载几十MB的完整包才能更新。
再看回Android的状况,Android热修复和iOS是有极大不一样的。主要有两个方面:
谷歌在中国没有像苹果那样的控制力,即便它想要封杀也不可能,国内是有各个安卓应用市场的,没有统一的app安装渠道。另外,Android是开源的,各个厂商均可以作定制,想统一各家的安装渠道几乎是不可能的。
咱们对于将来是很乐观的,Android的热修复领域不只不会受到封杀,反而还有很大的发展空间。咱们正在尝试支持各大加固厂商,目前阿里聚安全修复已经支持了Sophix,热修复结合安全加固,将会使得app的稳定性和安全性更加坚如盘石。甚至后续还能够与系统厂商合做,对系统app乃至系统组件进行修复,这样就能够避免频繁OTA升级。
所以,热修复所能发挥是价值将是十分巨大的。热修复还能够与其余领域进行碰撞,引起无限的可能性。在这里,咱们欢迎全部应用厂商以及ROM厂商与咱们合做,共同使得Android的生态更加完善。