一、关于前面四篇博文
Android热补丁动态修复技术(一):从Dex分包原理到热补丁
Android热补丁动态修复技术(二):实战!CLASS_ISPREVERIFIED问题!
Android热补丁动态修复技术(三)—— 使用Javassist注入字节码,完成热补丁框架雏形(可使用)
Android热补丁动态修复技术(四):自动化生成补丁——解决混淆问题
前两篇博文主要是介绍热补丁修复技术的一些原理和实现方案。
而后面两篇博文主要是介绍如何使用代码实现整个热补丁框架,但是框架写的真的很糟糕,很多多余的操作。而这很大一部分原因是使用了transform,在混淆的时候transform并不好用。
以下是我在github上重构好的热补丁框架,求star (??`ω′?)
https://github.com/AItsuki/HotFix
1. 支持混淆
2. 自动生成带签名的补丁包
3. 加载补丁包时会进行签名校验
图中的patch文件夹就是自动生成的补丁包保存目录了,里面有打成jar包之前的class,如果patch.jar打包失败,还能继续手动打包。
更详细的介绍和使用方式请移步到github,再说一次:求star (??`ω′?)
二、框架的实现思路
在第四篇博文中,我们发现在混淆的情况下,transform使用起来真的很反人类,因为transform只能在混淆之前对class进行操作,无法将transform添加到混淆之后。
所以以下思路,我放弃了使用transform,而是直接在dextransform这个任务的dofirst中进行操作。
在重构项目之前,我先记录下了这些思路和流程,然后根据这个流程来实现热补丁框架,效率真的快了很多。
2.1 定义热补丁框架的使用方式
- release签名打包作为发布版本,每次release打包都会重新生成hash.txt和mapping.txt(开启混淆的情况下才有mapping)
- 每次debug运行的时候(直接运行项目或者buildapk),都会通过校验hash.txt和mapping.txt生成已签名补丁包。
直接将补丁包放到sdcard中即可完成热修复
- 加载补丁的时候需要进行签名校验,防止恶意代码注入
2.2 代码流程
抛弃transform,使用纯hook的方式实现。
主要hook的task有这几个:
- transformClassesWithDexForRelease
- transformClassesWithDexForDebug
- transformClassesAndResourcesWithProguardForRelease
- transformClassesAndResourcesWithProguardForDebug
不混淆的情况:
transformClassesWithDexForRelease
dofirst —— 遍历输入文件,生成md5保存好(hash.txt),然后注入代码
transformClassesWithDexForDebug
dofirst —— 遍历输入文件,生成md5,和hash对比,将改变过的类复制到补丁文件夹,然后注入代码
混淆的情况:
transformClassesAndResourcesWithProguardForRelease
dolast —— 遍历输出文件,生成md5保存好(hash.txt),然后注入代码,将mapping保存好
transformClassesAndResourcesWithProguardForDebug(需要使用applymapping)
dolast —— 遍历输出文件,生成md5,和hash对比,将改变过的类复制到补丁文件夹,然后注入代码
开启混淆后task的执行顺序是proguard –> dex
因为dex永远是在最后面执行,所以注入代码和生成补丁这些操作都只需要hook dex就可以了
但是开启混淆的时候,dex dofirst需要做的事情还是有点不同的,我们可以通过一个变量来控制 def minify = false
hook proguard,在proguardTransform执行的时候复制minify = true
这样就可以控制混淆和不混淆两种情况了。
2.3 实际遇到的问题
1、 不clean项目,第二次运行release打包不会注入代码
这是因为gradle的增量式构建,up-to-date,task不执行
解决方式:
dexRelease.outputs.upToDateWhen {false} 让task一直都执行
http://stackoverflow.com/questions/7289874/resetting-the-up-to-date-property-of-gradle-tasks
2、如果有使用到自定义控件,在xml的preView窗口会报空指针异常
这是因为自定义控件已经被注入了代码,而预览窗口的时候并没有加载hack.jar,找不到AntilazyLoad.class,所以报空指针。
解决方式:
使用pluginExtention,在build.gradle中配置变量,控制在debug模式下是否注入代码。
如图,这里添加了两个Extention
3、如何applymapping
applymapping的作用是复用上一次的混淆规则。
所以我们需要将release生成的mapping.txt应用到debug的混淆上,否则可能无法正确的生成补丁。
解决方式:
第一种:
手动配置debug的混淆文件
第二种:
1. 在gradle 1.5以下时,可以直接task.applyMapping(File file)的方式在代码中动态添加
2. 在gradle1.5以上时,因为proguard的transform是一个特殊的task,所以并不能直接applyMapping,需要做一些强转。
(proguardDebug即transformClassesAndResourcesWithProguardForDebug)
4、开启混淆后的Release签名打包,如果debug模式不开启混淆的话,会将所有类都打包成补丁。
这是因为,如果debug模式不开启混淆,那么就会拿不混淆的代码和Release已经混淆的代码进行校验,md5肯定不一致,所以会将所有类打包成补丁包。
解决方式:
暂时没有好办法,老老实实开启混淆吧。Debug是否开启混淆要和Release保持一致
5、如何签名补丁
补丁的签名主要用到的是jdk的工具,jarsigner.exe。使用代码调用命令行即可
6、如何进行签名校验
首先,debug安装的app不需要进行校验,这是检测当前app是否是debug签名的方法。
http://blog.csdn.net/luohai859/article/details/44679085
然后,这是校验补丁包和app签名是否一致
http://blog.csdn.net/hudashi/article/details/8245105
7、android 6.0无法从sdcard加载补丁包
运行时权限机制的问题,可以将补丁包放到app私有空间加载。
8、 androidStudio 2.0以上用到了instantRun,这是否会对debug自动生成补丁包产生影响。
这个问题我还没有测试,如果真的有影响的话也有很简单的解决方式,直接使用签名打包debug也可以生成补丁包。
三、参考项目
https://github.com/jasonross/Nuwa
https://github.com/bunnyblue/DroidFix
https://github.com/Livyli/AndHotFix
主要是第三个,签名校验的思路来源于它
四、写在后面
终于算是完成了热补丁框架了,其中过程真的累人啊!
整个框架的实现思路比较清晰简单,代码量也不超过1000行,很适合正在学习这个技术的朋友们。
求star,求star,第一个上传到github的项目求star (??`ω′?)
https://github.com/AItsuki/HotFix