什么是热修复
我们知道按照现有的模式,一旦我们上线的app版本有了bug,我们不得不先解决bug,然后测试无误后,重新打包,然后再发布到应用市场上去,这一系列的操作,不仅浪费时间,而且也影响用户的体验,若能像web端一样,更改了代码就能立马生效,那般轻松也正是开发者有朝一日所希望的。现如今Android插件化热更新技术非常火热,也开源了很多的项目如Dexposed,AndFix,ClassLoader,nuwa等等。说来说去,热修复的作用就是可以动态的修复你的bug,一旦有bug要修复,就可以通过事先的接口从网上下载无bug的代码来替换有bug的代码,这样就省事多了,用户体验也好。
热修复的原理
Android的类加载机制分为两种,PathClassLoader(用来加载系统类和应用类)和DexClassLoader(用来加载jar,apk,dex文件,加载jar,apk也是最终抽取里面的Dex文件进行加载),并且他们都继承BaseDexClassLoader。
1.看下pathClassLoader代码
|
|
2.DexClassLoader代码
|
|
两个ClassLoader就两三行代码,只是调用了父类的构造函数
3.BaseDexClassLoader代码
|
|
在BaseDexClassLoader构造函数中创建一个DexPathList类的实例,这个DexPathList的构造函数会创建一个dexElements数组
|
|
然后BaseDexClassLoader重写了findClass方法,调用了pathList.findClass,跳到DexPathList类中。
|
|
会遍历这个数组,然后初始化DexFile,如果DexFile不为空那么久调用DexFile类的loadClassBinaryName方法返回Class实例,总结就是ClassLoader会遍历这个数组,然后加载这个数组中的dex文件,而ClassLoader在加载了正确的类后,就不会去加载有bug的那个类了,我们把这个正确的类放在Dex文件中,让这个Dex文件排在dexElements数组前面即可。
AndFix实践
最近我刚在公司的一个项目中添加了热修复,使用的是阿里的AndFix.
AndFix支持Android2.3到7.0版本,并且支持arm和x86系统架构的设备,完美支持Dalvik和ART的Runtime。
AndFix原理
他的原理如图所示,就是方法替换,把有bug的方法替换成补丁文件中的方法。
方法替换过程:
AndFix使用
1.首先我们要添加AndFix依赖
|
|
2.在Application中初始化PatchMannger
|
|
appVersion获取方法:
|
|
3.打包一个修复了bug的同版本号的apk
4.使用官方提供的工具apkpatch生成.apatch补丁文件
点击上面的链接下载apkpatch之后解压
5.将两个apk文件和该app的签名文件放入到该目录中
6.通过命令行进入到该目录下,然后使用命令行
apkpatch.bat -f 新apk -t 旧apk -o 输出目录 -k app签名文件 -p 签名文件密码 -a 签名文件别名 -e 别名密码
7.在输出目录下就有一个.aptch文件,这个文件就apatch补丁文件,改名为fix.apatch(随意)
8.在公司的项目中,我使用的是通过推送将该补丁文件推送到用户本地去,然后通过自动下载来更新bug。我们使用的推送是集成个推,个推推送的透传消息,就可以传json键值对,以下就是我的PushBean实体类中的字段。
|
|
9.当收到透传消息,判断type类型,如果type等于apatch,则说明需要热更新修复了
|
|
10.判断当前的版本是否有补丁需要下载更新,如果服务器端的应用版本和本地的应用版本一样,但是补丁版本(版本自己定,不一样即可)不一样,则需要下载更新
|
|
11.下载补丁文件,在项目中使用的是Nohttp,该网络请求框架非常的方便,并且都实现了对进度的监听,所以如果你的项目中需要下载或上传很多文件,推荐使用Nohttp。
|
|
12.创建DownLoadListener监听,在文件下载完后,就可以
调用全局的PatchManager对象通过addPatch方法来加载该文件
|
|
13.到此,补丁文件被加载后,bug就修复了,就是这么简单,最后附上RepairBugUtil工具类
|
|
AndFix优缺点
最后由于AndFix采用native hook的方式,这套方案直接使用dalvik_replaceMethod替换class中方法的实现。由于它并没有整体替换class, 而field在class中的相对地址在class加载时已确定,所以AndFix无法支持新增或者删除filed的情况(通过替换init与clinit只可以修改field的数值)。
也正因如此,Andfix可以支持的补丁场景相对有限,仅仅可以使用它来修复特定问题。结合之前的发布流程,我们更希望补丁对开发者是不感知的,即他不需要清楚这个修改是对补丁版本还是正式发布版本(事实上我们也是使用git分支管理+cherry-pick方式)。另一方面,使用native替换将会面临比较复杂的兼容性问题。
相比其他方案,AndFix的最大优点在于立即生效。事实上,AndFix的实现与Instant Run的热插拔有点类似,但是由于使用场景的限制,微信在最初期已排除使用这一方案。