程序员人生 网站导航

Android热补丁技术方案整理

栏目:综合技术时间:2016-08-16 18:20:16

概述

项目快速迭代进程中,不可避免的出现BUG,Android线上出现问题,通常需要发版解决。紧急发版,用户不1定升级,强迫升级又不友好,有甚么更好的解决方案呢?这就用到了热修复技术。

QQ团队的hotfix

hotfix,后来发展成为RocooFix,

GitHub地址: https://github.com/dodola/HotFix

原理详细介绍官方文章:安卓App热补钉动态修复技术介绍

HotFix存在的问题:这类方法没法在已加载好的类中实现动态替换,只能在类加载之前替换掉。就是说,补钉下载下来后,只能等待用户重启利用才能完成补钉效果。

RocooFix支持两种模式:
静态修复某种情况下需要重启利用。
动态修复,无需重启利用便可生效。

补钉制作

该技术的原理很简单,其实就是用ClassLoader加载机制,覆盖掉有问题的方法。所以我们的补钉其实就是有问题的类打成的1个包。

例子中的出现问题的类是 dodola.hotfix.BugClass 原始代码以下:

public class BugClass { public String bug() { return "bug class"; } }

我们假定BugClass类里的bug()方法出现毛病,需要修复,修复代码以下:

public class BugClass { public String bug() { return "fixed class"; } }

那末我们只需要将修复过的类编译后打包成dex便可

步骤以下:

  1. 将补钉类提取出来到1个文件夹里

  2. 将class文件打入1个jar包中 jar cvf path.jar *

  3. 将jar包转换成dex的jar包 dx --dex --output=path_dex.jar path.jar

这样就生成了补钉包path_dex.jar

实现javassist动态代码注入

实现这1部份功能的缘由主要是由于出现以下异常

java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation 

问题缘由在文档中已描写的比较清楚。

就是如果以上方法中直接援用到的类(第1层级关系,不会进行递归搜索)和clazz都在同1个dex中的话,那末这个类就会被打上CLASS_ISPREVERIFIED

很明显,解决的方法就是在类中援用1个其他dex中的类,但是源码方式的援用会将援用的类打入同1个dex中,所以我们需要找到1种既能编译通过并且将两个相互援用的类分离到不同的dex中,因而就有了这个动态的代码植入方式。

首先我们需要制作援用类的dex包,代码在hackdex中,我直接使用了文档中的类名 AntilazyLoad 这样可以和文章中对应起来,方便1些。

我们将这个库打包成dex的jar包,方法跟制作补钉1样。

下面是重点,我们要用javassist将这个类在编译打包的进程中插入到目标类中。

为了方便,我将这个进程做成了1个Gradle的Task,代码在buildSrc中。

这个项目是使用Groovy开发的,需要配置Groovy SDK才可以编译成功。

核心代码以下:

/** * 植入代码 * @param buildDir 是项目的build class目录,就是我们需要注入的class所在地 * @param lib 这个是hackdex的目录,就是AntilazyLoad类的class文件所在地 */ public static void process(String buildDir, String lib) { println(lib) ClassPool classes = ClassPool.getDefault() classes.appendClassPath(buildDir) classes.appendClassPath(lib) //下面的操作比较容易理解,在将需要关联的类的构造方法中插入援用代码 CtClass c = classes.getCtClass("dodola.hotfix.BugClass") println("====添加构造方法====") def constructor = c.getConstructors()[0]; constructor.insertBefore("System.out.println(dodola.hackdex.AntilazyLoad.class);") c.writeFile(buildDir) CtClass c1 = classes.getCtClass("dodola.hotfix.LoadBugClass") println("====添加构造方法====") def constructor1 = c1.getConstructors()[0]; constructor1.insertBefore("System.out.println(dodola.hackdex.AntilazyLoad.class);") c1.writeFile(buildDir) growl("ClassDumper", "${c.frozen}") }

下面在代码编译完成,打包之前,履行植入代码的task就能够了。

在 app 项目的 build.gradle 中插入以下代码

task('processWithJavassist') << { String classPath = file('build/intermediates/classes/debug')//项目编译class所在目录 dodola.patch.PatchClass.process(classPath, project(':hackdex').buildDir .absolutePath + '/intermediates/classes/debug')//第2个参数是hackdex的class所在目录 } android{ ....... applicationVariants.all { variant -> variant.dex.dependsOn << processWithJavassist //在履行dx命令之前将代码打入到class中 } }

反编译编译后的apk可以发现,代码已植入进去,而且包里其实不存在dodola.hackdex.AntilazyLoad 这个类


阿里巴巴的AndFix

GitHub地址: https://github.com/alibaba/AndFix

使用步骤

初始化

patchManager = new PatchManager(context); patchManager.init(appversion);//current version
加载patch
patchManager.loadPatch();
添加patch文件
patchManager.addPatch(path);
支持热更新,不需要重新启动

阿里巴巴的dexposed

GitHub地址: https://github.com/alibaba/dexposed

大众点评的Nuwa

Nuwa的具体实现也是根据QQ空间的热修复方案来实现的

GitHub地址: https://github.com/jasonross/Nuwa

DroidFix

GitHub地址: https://github.com/bunnyblue/DroidFix

官方介绍: http://bunnyblue.github.io/DroidFix/

DroidFix的实现原理跟QQ空间的热补钉方案类似。

携程的DynamicAPK

GitHub地址: https://github.com/CtripMobile/DynamicAPK

主流热补钉开源方案基本上就是以上这些。



欢迎扫描2维码,关注公众号


------分隔线----------------------------
------分隔线----------------------------

最新技术推荐