1.Android热修复
热修复,就是对线上版本的静默更新。当APP发布上线之后,如果出现了严重的bug,通常需要重新发版来修复,但是重新走发布流程可能时间比较长,重新安装APP用户体验也不友好,所以出现了热修复,热修复就是通过发布一个插件,使APP运行的时候加载插件里面的代码,从而解决缺陷,并且对用户来说是无感的(有时候可能需要重启一下APP)。
热修复的实现方案,一种是类加载方案,即dex插桩,这种思路在插件化中也会用到;还有一种是底层替换方案,即修改替换ArtMethod。采用类加载方案的主要是以腾讯系为主,包括微信的Tinker、qq空间的QZone、美团的Robust、饿了么的Amigo;采用底层替换方案的主要是阿里系的AndFix等。
热修复包括3部分:开发端、服务端和用户端。在开发端,通过Gradle插件生成补丁包,并上传到云端,客户端通过判断是否需要下载新的补丁包,并执行热修复。
目前主流的热修复框架有腾讯的Tinker、QZone,阿里的AndFix、Sophix,美团的Robust。他们采用的修复方式不同,比如AndFix和Robust采用native层hook Java层代码 bug fix,他们是即时生效的;而Tinker和QZone采用类替换,需要重启APP才能生效。
①阿里的AndFix(已经不再维护)
在native层动态替换java层的方法,通过native层hook java层的代码。通过在native层实现热修复,是不需要重启修复,这是即时生效的。
例如方法B中有bug,需要通过热修复替代这个方法。我们知道,所有方法的调用,都会在JVM中入栈,执行完成之后出栈,方法在JVM中是一个ArtMethod结构体,那么在JVM运行这个方法之前,在Native层完成这个方法的替换,那么就完成了热修复的工作,而且是即时生效的。这是基于方法进行修复的。
②美团的Robust
对每个函数都在编译打包阶段自动的插入一段代码,类似于代理,将方法执行的代码重定向到其他方法中。
Robust采用的技术是编译时字节码插桩技术,这个过程在gradle-plugin中发生,在编译打包阶段,对每个函数注入一段逻辑代码,通过判断是否执行插入的这段代码,这个过程也是即时生效的。
③腾讯的Tinker
Tinker采用的是Dex动态加载技术,通过反射的方式,将待修复的类放在dexElements数组的前面,在类加载的时候,首先加载这个待修复的类,因为类加载机制不会重复加载类,达到修复的目的。但这个方式是需要重启生效的(出现bug的类在ClassLoader中是不能替换的,存在缓存中,只能重启重新进行类加载)。
Tinker通过计算对比指定的Base Apk中的dex与修复后的Apk中的dex的区别,生成补丁包,所以补丁包中的内容即为两者差分的描述。运行时将Base Apk中的dex与补丁包进行合成,重启后加载全新的合成后的dex文件。
以上3种方式是目前热修复常见的3种方式,其实各有利弊,像native层处理需要大量的开发成本,跟Robust一样,只能达到修复bug的目的,不能新增类和轻量级的功能;而Tinker则是需要重启才能生效。
热修复用到的技术包括ClassLoader类加载机制、Dex动态加载技术 – hook反射、差分打包技术 – bsdiff、字节码插桩 – ASM或Javassist、Gradle插件 – 发布差分包、so库的编译。
2.热修复流程
根据类加载机制,可以知道热修复的原理就是将补丁包dex文件放到dexElements数组靠前的位置,这样在加载class时,优先找到补丁包中的dex文件,加载到class之后就不再寻找,从而原来apk里同名的类就不会再使用,达到修复的目的。
知道了原理,实现就很简单了,就是添加新的dex对象到当前app的classLoader对象(也就是BaseDexClassLoader)的pathList里面的dexElements。要添加就要先创建,先使用DexClassLoader加载插件,然后再生成插件的dexElements,最后再添加就好了。
①获取到当前应用的PathClassLoader;
②反射获取到DexPathList属性对象pathList;
③反射修改pathList的dexElement:
(1)把补丁包patch.dex转化为Element[](patch)
(2)获得pathList的dexElements属性(old)
(