移动热修复(Mobile Hotfix)是阿里云提供的全平台App热修复服务方案。产品基于阿里巴巴首创hotpatch技术,提供最细粒度热修复能力,让您无需等待实时修复应用线上问题。当产品已有功能出现问题,无需发版,即可发补丁,实时修复。
Hotfix产品优势:
跨平台支持
支持Android平台。同一版本的Android端首次被打补丁时,不用重启直接加载(支持热启动);支持iOS平台补丁版本管理
开发者可以在控制台创建多个版本,同时管理多个补丁,方便查看补丁状态本地测试
提供了调试工具实现本地测试,方便开发者在正式发布前,在自己的手机本地进行测试。多种发布操作
提供多种发布方式,方便开发者根据自身业务需要选择性使用。灰度发布、全量发布、发布停止、发布回滚数据统计
可以统计:成功推送设备数:每当有设备发起一次更新请求,且补丁下载成功,则记为一次成功推送;累计加载设备数:每当有设备成功加载该补丁,则记为一次累计加载。补丁一键清除
使用补丁一键清除功能,开发者可以对指定应用版本下面的所有补丁进行一键清除操作,用户下次打开应用时,将自动清除本地所有补丁,回滚至无补丁状态。
一步一步接入Hotfix SDK
我自己是按照官方文档做成功了一遍,做了一个app,写了一个崩溃处,为问题apk,然后进行处理崩溃,再打出修复版apk,生成补丁文件,发布版本,然后原有崩溃出自动修复。
-
登录阿里云账号后,进入移动研发平台,创建产品。
配置项目信息
根据填写项目信息生成json配置文件,下载然后拷贝到项目根目录下。
- 配置项目级目录下build.gradle文件。
buildscript {
repositories {
google()
jcenter()
maven {
url 'http://maven.aliyun.com/nexus/content/repositories/releases/'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
// 添加emas-services插件
classpath 'com.aliyun.ams:emas-services:1.0.1'
}
}
allprojects {
repositories {
google()
jcenter()
maven {
url 'http://maven.aliyun.com/nexus/content/repositories/releases/'
}
}
}
配置app目录下build.gradle文件
apply plugin: 'com.aliyun.ams.emas-services'
在移动平台创建应用完成,然后开始创建补丁。
1.创建补丁版本
2.下载补丁生工工具。分别上传bug安装包和已修复版安装包,安装包需要正式签名工具打包生成。
-
填写签名文件keystore信息。
基础选项
旧包:<必填> 选择基线包路径(有问题的APK)。
新包:<必填> 选择新包路径(修复过该问题APK)。
日志:打开日志输出窗口。
高级:展开高级选项,见1.2.2。
设置:配置其他信息。
GO!:开始生成补丁。
高级选项
强制冷启动:勾选的话强制生成补丁包为需要冷启动才能修复的格式。默认不选的话,工具会根据代码变更情况自动选择即时热替换或者冷启动修复。
不比较资源:打补丁时不比较资源的变化。
不比较SO库:打补丁时不比较SO库的变化。
4.弹出打包前注意事项弹框
-
生成新旧包差异补丁文件成功。
6.上传补丁文件
7.发布补丁,自动修复bug。
灰度发布
发布类型分为灰度发布和全量发布,顾名思义,全量发布就是一次性发布给所有用户使用。
灰度策略
需要根据用户体量来决定发布人数,也可以根据产品需求来决定。一般可以投放10%的量来观察。
我们可以通过用户id、用户手机号、设备id的尾号来决定给哪些用户推送升级信息。
阿里Hotfix热修复原理
Andfix 采用的方法是,在已经加载了的类中直接在 native 层替换掉原有方法,是在原来类的基础上进行修改的。其核心在于 replaceMethod 函数,所以只支持方法替换,对于方法的增删,资源更新,so 文件更新及类和属性的替换等都是不支持的。
优点:
即时生效
缺点:
AndFix不支持新增方法,新增类,新增field等
方法替换:
腾讯Tinker 热修复实现原理
使用了反射的机制,将下载到本地的补丁 dex 文件放到baseDexClassLoader的pathList下的dexElements 数组靠前位置,这样在加载 class 时,优先找到补丁包中的 dex 文件,加载到 class 之后就不再寻找,从而原来的 apk 文件中同名的类就不会再使用,从而达到修复的目的。
1.构造加载了补丁资源的新AssetManager替换原有的AssetManager
2.将原有代码中所有引用到旧AssetManager的地方修改为新AssetManager
优点:
支持动态下发代码
支持替换So库以及资源
缺点:
不能即时生效,需要下次启动
实现示例:
//在Application中进行替换
public class MApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//dex作为插件进行加载
dexPlugin();
}
...
/**
* dex作为插件加载
*/
private void dexPlugin(){
//插件包文件
File file = new File("/sdcard/FixTest.dex");
if (!file.exists()) {
Log.i("MApplication", "插件包不在");
return;
}
try {
//获取到 BaseDexClassLoader 的 pathList字段
// private final DexPathList pathList;
Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
//破坏封装,设置为可以调用
pathListField.setAccessible(true);
//拿到当前ClassLoader的pathList对象
Object pathListObj = pathListField.get(getClassLoader());
//获取当前ClassLoader的pathList对象的字节码文件(DexPathList )
Class<?> dexPathListClass = pathListObj.getClass();
//拿到DexPathList 的 dexElements字段
// private final Element[] dexElements;
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
//破坏封装,设置为可以调用
dexElementsField.setAccessible(true);
//使用插件创建 ClassLoader
DexClassLoader pathClassLoader = new DexClassLoader(file.getPath(), getCacheDir().getAbsolutePath(), null, getClassLoader());
//拿到插件的DexClassLoader 的 pathList对象
Object newPathListObj = pathListField.get(pathClassLoader);
//拿到插件的pathList对象的 dexElements变量
Object newDexElementsObj = dexElementsField.get(newPathListObj);
//拿到当前的pathList对象的 dexElements变量
Object dexElementsObj=dexElementsField.get(pathListObj);
int oldLength = Array.getLength(dexElementsObj);
int newLength = Array.getLength(newDexElementsObj);
//创建一个dexElements对象
Object concatDexElementsObject = Array.newInstance(dexElementsObj.getClass().getComponentType(), oldLength + newLength);
//先添加新的dex添加到dexElement
for (int i = 0; i < newLength; i++) {
Array.set(concatDexElementsObject, i, Array.get(newDexElementsObj, i));
}
//再添加之前的dex添加到dexElement
for (int i = 0; i < oldLength; i++) {
Array.set(concatDexElementsObject, newLength + i, Array.get(dexElementsObj, i));
}
//将组建出来的对象设置给 当前ClassLoader的pathList对象
dexElementsField.set(pathListObj, concatDexElementsObject);
} catch (Exception e) {
e.printStackTrace();
}
}
参考:
Android热修复原理及实现
https://www.cnblogs.com/popfisher/p/8543973.html