相信每个开发者在app版本上线后才发现有一个致命性崩溃的bug时,心中是一万只草泥马在奔跑!
每次发现这种bug,都只好立马上个小版本修复。这种体验着实糟糕。
那我们能不能动态加载一小部分代码来修复这个bug呢?
今天给大家带来了女娲nuwa热修复!

一、什么是热修复?

热修复补丁(hotfix),又称为patch,指能够修复软件漏洞的一些代码,是一种快速、低成本修复产品软件版本缺陷的方式。
具体原理可以查看鹅厂qq空间热修复。

二、如何使用?

从QQ空间终端开发团队的文章中可以总结出要进行热更新只需要满足下面两点就可以了:
1.动态加载补丁dex,并将补丁dex插入到dexElements最前面
2.要实现热更新,需要热更新的类要防止被打上ISPREVERIFIED标记,关于这个标记,请阅读上面QQ空间团队的文章。
对于第一点,实现很简单,通过DexClassLoader对象,将补丁dex对象加载进来,再通过反射将补丁dex插入到dexElements最前面即可。具体可参考谷歌的Multidex的实现。

而对于第二点,关键就是如何防止类被打上ISPREVERIFIED这个标记。

简单来说,就是将所有类的构造函数中,引用另一个hack.dex中的类,这个类叫Hack.class,然后在加载补丁patch.dex前动态加载这个hack.dex,但是有一个类的构造函数中不能引用Hack.class,这个类就是Application类的子类,一旦这个类的构造函数中加入Hack.class这个类,那么程序运行时就会找不到Hack.class这个类,因为还没有被加载。也就是说,一个类直接引用到的类不在同一个dex中即可。这样,就能防止类被打上ISPREVERIFIED标记并能进行热更新。

添加Gradle Plugin
在工程的根build.gradle文件中添加

1
classpath 'cn.jiajixin.nuwa:gradle:1.2.2'

然后我们的build.gradle文件可能长这样:

1
2
3
4
5
6
7
8
9
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
classpath 'cn.jiajixin.nuwa:gradle:1.2.2'
}
}

添加Nuwa SDK

在app的build.gradle中添加依赖:

1
apply plugin: "cn.jiajixin.nuwa"

然后我们的build.gradle文件可能长这样:

1
2
3
4
5
6
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'cn.jiajixin.nuwa:nuwa:1.0.0'
}

【备注】 这边有个坑,2.0版本的Android Studio为了支持Instant Run, 默认使用的Gradle Plugin是比较高的, 如果使用高版本,会报错Error:Cannot get property ‘taskDependencies’ on null object,乖乖降低到把你的Gradle Plugin版本降到较低版本,我用的是1.2.3,木有此问题

使用Nuwa

1.自定义Application,并且在Application类中添加以下方法:

1
2
3
4
5
6
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Nuwa.init(this);
Nuwa.loadPatch(this,"/sdcard/patch.jar")
}

2.运行并安装apk,会在app/build/outputs/目录下自动生成一个叫nuwa的目录:

拷贝此目录到一个指定的目录,比如:

1
/Users/Documents/nuwa

此apk目前是有bug的apk, 然后我们修复bug, 修复完毕后进入步骤3

3.在Terminal工具执行如下命令:

1
2
#mac使用./gradlew或gradle,windows使用gradlew
./gradlew clean nuwaDebugPatch -P NuwaDir=/Users/Documents/nuwa

如果你使用的mac并且无法使用./gradlew,报:Permission denied错误,请执行一次该命令:

1
chmod +x gradlew

然后在app/build/outputs/nuwa/debug/目录下会自动生成patch.jar文件:

此文件就是我们热部署的补丁文件

4.我们把补丁文件拷贝到sdcard根目录(这边只是为了测试整个hotfix流程, 自己的项目肯定是放在服务器上,我们通过代码自己去下载和存储)

1
adb push app/build/outputs/nuwa/debug/patch.jar /sdcard/

5.重新启动app,Bug就热修复完成

Tips

如果你使用了多渠道配置,以Androidmarket为例

1
2
3
4
5
6
7
productFlavors {
Androidmarket {}
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}

执行第三步的时候命令有些区别:

1
./gradlew clean nuwaAndroidmarketDebugPatch -P NuwaDir=/Users/Documents/nuwa

如果打的是Release版本,使用以下代码

1
./gradlew clean nuwaAndroidmarketReleasePatch -P NuwaDir=/Users/Documents/nuwa

三、有哪些坑?

1.Application类尽可能少引用其他类,以免造成该类被打伤标记无法进行热修复;
2.热修复无法修复xml(特别是manifest)、资源,所以如果你的布局或者manifest出错,尽量看看能否在代码里修改,不能的话那就放弃热修复还是使用常规升级修复;
3.使用命令生成patch.jar时,有修改的类会连同向下依赖的类一起打包进去,因此打出来包比你想象中大点,如果没有打进去,可能会造成ClassNotFoundException错误;