线上bug 使用andfix修复流程(待修订)
- 出现问题,明确是bug后,找到相应的模块负责人
- 找出bug 影响的版本和范围,拉出相应分支,修改bug
- 用修复后的包打出补丁包,并保存修复后的包,把补丁包用adb push 到相应的目录下测试(测试方案待定)
- 在后台上传补丁包,并测试补丁的更新机制
- 在git 上建立 issue ,做好事故的描述,影响范围,解决方案
下面是对某些问题的解释,也是制定的方案,比较杂
Andfix 适配性
- 官方的文档和代码说的是支持2.3-7.0 ,不支持yunOS
- 部分机型奔溃问题,查看了大部分的奔溃发现都是在jni 上,有的是因为so 没有适配,有的是android jni 的bug ,在最新的0.5.0 基本都解决了
初始化版本 patchManager.init(appVersion)
- 每一个版本对应的补丁也是不同的,如果在版本更新后,就不能使用老的补丁了,所以andfix 在init 时做了缓存,如果当前的版本和缓存的版本不一致,andfix 会认为更新了版本,则会删除所有的补丁。所以,建议这里的appVersion 使用app 的版本,也就是packageInfo的versionName
public void init(String appVersion) {
if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail
Log.e(TAG, "patch dir create error.");
return;
} else if (!mPatchDir.isDirectory()) {// not directory
mPatchDir.delete();
return;
}
SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,
Context.MODE_PRIVATE);
String ver = sp.getString(SP_VERSION, null);
if (ver == null || !ver.equalsIgnoreCase(appVersion)) {
cleanPatch(); //清除所有patch
sp.edit().putString(SP_VERSION, appVersion).commit();
} else {
initPatchs();
}
}
补丁的规范
- 补丁的命名暂时使用 version-patchNum.apatch 的样式, 例如 1.0.0-1.apatch
- 必须以apatch 结尾,否则andfix是不会进行后续的操作
- 每次补丁的名字都不要一致,不然也是不能进行修复的
private static final String SUFFIX = ".apatch";
private Patch addPatch(File file) {
Patch patch = null;
if (file.getName().endsWith(SUFFIX)) { //补丁文件必须要以.apatch 结尾
try {
patch = new Patch(file);
mPatchs.add(patch);
} catch (IOException e) {
Log.e(TAG, "addPatch", e);
}
}
return patch;
}
public void addPatch(String path) throws IOException {
File src = new File(path);
File dest = new File(mPatchDir, src.getName());
if(!src.exists()){
throw new FileNotFoundException(path);
}
if (dest.exists()) { //如果已存在同样名字的补丁,则无法进行后续的操作
Log.d(TAG, "patch [" + path + "] has be loaded.");
return;
}
FileUtil.copyFile(src, dest);// copy to patch's directory
Patch patch = addPatch(dest);
if (patch != null) {
loadPatch(patch);
}
}
补丁版本的缓存和 补丁的检测
- 对于版本的缓存,是用sharedPreferences 做的,用版本号做key ,补丁号为value ,默认的补丁号为0
- 用sharedPreferences的原因是为了andfix 保持同步,andfix 的补丁和缓存都放在了sharedPreferences下
- 每次检测时,取出(当前补丁号+1),当前版本号,项目名,交由服务器判断
- 补丁下载完成修复后,当前补丁号+1
- 每次检测补丁,只能修复一个补丁版本
补丁的增量更新
- 情况1 : bug 只存在v1.0.0 中,切到1.0.0,并建立v1.0.0-1,修复问题,用2个分支打出的apk包 ,打出v1.0.0-1.apatch 的补丁,如果v1.0.0 中还存在问题,则根据v1.0.0-1 建立v1.0.0-2 ,打出v1.0.0-2.apatch 的补丁,不要做跳跃修复(即不可以直接在v1.0.0 和v1.0.0-2 中打出补丁)
- 情况2 : bug 只存v1.0.0 中,同时影响了v1.0.1 或者v1.0.1-1,这样的情况就比较复杂了,需要根据实际情况做讨论(所以尽量少些bug,后续要是影响几个版本的话,补丁会打到死)
新增问题,补丁包的版本缓存
- 在一开始是这么做的
mPatchManager.addPatch(patchPath); // 加载补丁
// 在加载补丁后,做补丁版本的缓存
APactchCache cache = new APactchCache(this); // 获取补丁的缓存类
String versionName = AppKit.getVersionNameInApk(this); //获取app版本
int code = cache.getPatchCode(versionName);// 获取当前的补丁号
cache.cachePatchCode(versionName, ++code);// 把当前的补丁号加1,在缓存
- 后来发现问题,addPatch的操作如果在底层报错,或者不起作用,后续的缓存代码还是会执行,就会产生后续的补丁和补丁号不一致的情况
- 暂时想到一个解决方案, 这样写的好处是保证了修复的代码和版本号一致的问题,只有代码修复成功,补丁号才会缓存,保证了线上代码的稳定性,缺点:不是很智能,如果修改多个方法,只能在一个方法中加入这段代码(在这里不能做上面的++操作,一定只能写死)
public void bugMethod(){
//.........
//上面是修复好的代码
APactchCache cache = new APactchCache(this); // 获取补丁的缓存类
String versionName = AppKit.getVersionNameInApk(this); //获取app版本
cache.cachePatchCode(versionName, 2);// 直接写入补丁号
}
- 2016年9月10号更新,针对这个问题,考虑了很多天,发现以上的方案都不稳定,又去看下源码, 发现一个更好的方案,简单的描述就是直接去andfix 自己的目录缓存下找补丁文件,获取当前的补丁版本
具体操作就是遍历这个文件夹的补丁文件,以当前app版本开头,apatch 结尾的都符合要求,然后在它们中找到最近的补丁号,用这个补丁号向服务器发起请求,解决了补丁号和补丁的同步问题,同时也不需要手动去处理补丁号
混淆后的补丁制作
- 有点烦,需要使用在 混淆文件中加入下面的语句 ,并配合mapping 文件来打出
-applymapping build/outputs/mapping/online/release/mapping.txt
-printmapping build/outputs/mapping/online/release/mapping.txt
补丁包的缓存
- 一开始的做法是方法sd卡缓存,然后调用andfix 去加载补丁,一旦加载成功后,sd卡缓存的补丁就没作用了,可以删除
作废
* 20160913 发现一个问题,某些测试平台的测试手机没有sd卡,所以,补丁的缓存地址改到了getFilesDir ,也就是adb 下的 /data/data/com.kqc.b2b/files/apatch_opt_temp下 (最终的补丁保存在/data/data/com.kqc.b2b/files/apatch_opt)
versionName = AppKit.getVersionNameInApk(mContext); //获取app版本
try {
File tempDir = new File(this.getFilesDir(), AndFixManager.DIR_TEMP);
if (tempDir.exists() || tempDir.mkdirs()) {
patchDirStr = tempDir.toString(); //apatch_opt_temp
}
// FileKit.getAppDirInExt(this, FileKit.AppDirTypeInExt.PATCH); //sd卡的缓存
} catch (Exception e) {
CrashReport.postCatchedException(e);
}
下面是修改后放到云平台测试的结果(测试方式为,编译一个老的apk包,没有bug ,用补丁包把代码故意改错,一旦热更新起作用,整个app 就无法使用)
出错上传到bugly,发现并没有因为使用热更新后,andfix 自身出错的问题(图中的错误为故意为之的错误)
这里发现一个问题,腾讯app云测随机50款测试,并没有什么作用,即使上传错误代码,也会出现正常的反馈。。。。
不过上面的测试反应出,andfix 本身还是不会报错的,特别是最近一期的bug修复后,andfix 还是稳定的。
20160914 真实环境的修复率测试 (补丁包内容为向服务器发送唯一设备号)
- 第一次测试腾云优测随机50台手机,41台手机修复成功,向服务端发送了设备号
- 第二次测试腾云优测随机50台手机,37台手机发送了设备号到我们后台(2台设备安装失败)
两次测试期间,bugly 并没有收到因为andfix 产生的错误信息。修复率在80%左右