感谢原作者!
项目地址:GitHub:https://github.com/dodola/HotFix
这里只介绍如何使用,以及使用中可能遇到的问题以及解决方案。下面介绍一个精简的案例。
一.制作补丁包
打包成功的文件结构如下
到这一步之前有三个操作 BugClass.java > BugClass.class > path.jar > path_dex.jar。
第一步 BugClass.java > BugClass.class
Dos中执行javac BugClass.java
即可。
第二步 打包com文件夹 > path.jar
jar cvf path.jar *
第三步 path.jar > path_dex.jar
dx --dex --output=path_dex.jar path.jar
不清楚什么原因,在第二步到第三步执行dx命令的时候一直报错:
为此花费了很多时间,在一篇博客中找到一种方案可以解决这个问题,在执行javac命令的时候这样写:
javac -source 1.6 -target 1.6 BugClass.java
这样在第三步执行dx命令不会出错。(http://blog.csdn.net/chichoxian/article/details/8760456)
我不确定问题所在,这里就不瞎唠叨了。
jar和dx命令
jar - Java\jdk1.8.0_51\bin\jar.exe
dx - Android\sdk\build-tools\23.0.3\dx.bat
我直接设置在环境变量
%JAVA_HOME%\bin\jar.exe
D:\Users\Administrator\AppData\Local\Android\sdk\build-tools\23.0.3
上面只是方便测试,所以使用了javac的命令去编译java文件,可以不这样操作。直接修改当前项目中的BugClass类,比如将返回值改为return fix class,然后编译项目,这样也可以得到BugClass.class文件,然后打包为patch_dex.jar,而且不会出现上面的错误。因为是测试嘛,所以还得把BugClass还原成以前模样,假设是 return bug class。然后再编译,这样本地的BugClass.class为return bug class,patch_dex.jar中BugClass.class为return fix class。
二.替换有问题的class文件
public class HotfixApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "path_dex.jar");
Utils.prepareDex(this.getApplicationContext(), dexPath, "path_dex.jar");
HotFix.patch(this, dexPath.getAbsolutePath(), "com.lxq.hotfix.BugClass");
try {
this.getClassLoader().loadClass("com.lxq.hotfix.BugClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
按官方给出的描述,我们要阻止相关类打上CLASS_ISPREVERIFIED标志,否则会报错(相关类的意思是,如果BugClass是错误的需要被替换的类,但是LoadBugClass又引用了BugClass,那么LoadBugClass就是相关类)
为什么会报错,可以看安卓App热补丁动态修复技术介绍该
简单点说就是效验dex的时候发现两个类不在同一个dex中,所以报错了,但是这个错误外面还有个if条件,如果该类打上CLASS_ISPREVERIFIED标志,才会执行dex效验。该方案的解决方式为往所有类的构造函数里面插入了一段代码,这段代码会引用另外一个dex中的类,这样就可以阻止相关类打上CLASS_ISPREVERIFIED标志了(具体可以阅读以上链接文章)。
但是,我现在想复现上面的错误,也就是按正常流程不插入代码,可以在案例中注释掉buildsrc项目PatchClass类中process方法里面的所有代码,看上图中的错误是否出现。
结论:
在Android 19 模拟器中会出现上图中的错误
在Android 23 模拟器中不会出现上图错误,并且能正确打上补丁,执行结果返回补丁类中的 return fix class。
猜测Android 23中dex效验是不是规则改变了。。。为了做到兼容,还是应该以Android 19为基准来测试。
另外我还遇到如下坑,导致耽误很很多时间
1.案例中的path_dex.jar,比如里面的文件是BugClass.class,这个class文件构造中也必须加上:
System.out.println(AntilazyLoad.class)
你可以修改app项目,比如BugClass中return fix class OK!,然后编译整个项目,然后找到BugClass.class文件,这个class文件构造中会加上这段代码,因为在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')//第二个参数是hackdex的class所在目录
}
android{
.......
applicationVariants.all { variant ->
variant.dex.dependsOn << processWithJavassist //在执行dx命令之前将代码打入到class中
}
}
这样你现在拿到的BugClass.class就可以制作补丁了。为了测试补丁效果,你还得把当前项目中刚才改动BugClass的代码还原回去,然后Build-ReBuild Project重新编译class文件。
2.每次运行项目后,下意识的想去看下class文件的构造中是否添加了
System.out.println(AntilazyLoad.class)
因为只有添加这段代码,打补丁才会有效,我当时用的 guolin大神的博客中的gui工具,发现class文件的构造中没有添加如上代码,以为是groovy的问题,看网上搭建环境啥的,好麻烦,最后实在没辙,换了一个gui工具,尼玛居然可以看到了。我用的android studio 2.1.2,项目中的buildsrc是可以运行的,看网上说要搭建环境,不是很理解。
这个错误是因为我的build.gradle版本是这样的
classpath 'com.android.tools.build:gradle:2.1.2'
应该改成:
classpath 'com.android.tools.build:gradle:1.3.0'
查了很多资料,还是无解。