前言
目前Android业内,热修复技术百花齐放,各大厂都推出了自己的热修复方案,使用的技术方案也各有所异,当然各个方案也都存在各自的局限性。在面对众多的方案,希望通过梳理这些热修复方案的对比及实现原理,掌握热修复技术的本质,同时也对项目接入做好准备。
什么是热修复技术?
关于热修复这个名词,并不陌生。相信大家都有过更新window补丁的经历,通过补丁可以动态修复系统的漏洞,只不过这个过程对用户而言是可选及自行操作。
那么关于Android平台的热修复技术,简单来说,就是通过下发补丁包,让已安装的客户端动态更新,让用户可以不用重新安装APP,就能够修复软件缺陷的一种技术。
为什么要使用热修复技术?
在回答这个问题之前,我觉得应该先思考如下几个问题。
- 开发上线的版本能保证不存在Bug么?
- 修复后的版本能保证用户都及时更新么?
- 如何最大化减少线上Bug对业务的影响?
从这些角度来说,相信大家应该都能有所体会,热修复技术带来的优势不言而喻。
- 可快速修复,避免线上Bug带来的业务损失,把损失降到最低。
- 保证客户端的更新率,无须用户进行版本升级安装
- 良好的用户体验,无感知修复异常。节省用户下载安装成本。
怎么选择热修复技术方案?
国内主流的技术方案
1、阿里系
名称 | 说明 |
---|---|
AndFix | 开源,实时生效 |
HotFix | 阿里百川,未开源,免费、实时生效 |
Sophix | 未开源,商业收费,实时生效/冷启动修复 |
HotFix是AndFix的优化版本,Sophix是HotFix的优化版本。目前阿里系主推是Sophix。
2、腾讯系
名称 | 说明 |
---|---|
Qzone超级补丁 | QQ空间,未开源,冷启动修复 |
QFix | 手Q团队,开源,冷启动修复 |
Tinker | 微信团队,开源,冷启动修复。提供分发管理,基础版免费 |
3、其他
名称 | 说明 |
---|---|
Robust | 美团, 开源,实时修复 |
Nuwa | 大众点评,开源,冷启动修复 |
Amigo | 饿了么,开源,冷启动修复 |
方案对比
方案对比 | Sophix | Tinker | nuwa | AndFix | Robust | Amigo |
---|---|---|---|---|---|---|
类替换 | yes | yes | yes | no | no | yes |
So替换 | yes | yes | no | no | no | yes |
资源替换 | yes | yes | yes | no | no | yes |
全平台支持 | yes | yes | yes | no | yes | yes |
即时生效 | 同时支持 | no | no | yes | yes | no |
性能损耗 | 较少 | 较小 | 较大 | 较小 | 较小 | 较小 |
补丁包大小 | 小 | 较小 | 较大 | 一般 | 一般 | 较大 |
开发透明 | yes | yes | yes | no | no | yes |
复杂度 | 傻瓜式接入 | 复杂 | 较低 | 复杂 | 复杂 | 较低 |
Rom体积 | 较小 | Dalvik较大 | 较小 | 较小 | 较小 | 大 |
成功率 | 高 | 较高 | 较高 | 一般 | 最高 | 较高 |
热度 | 高 | 高 | 低 | 低 | 高 | 低 |
开源 | no | yes | yes | yes | yes | yes |
收费 | 收费(设有免费阈值) | 收费(基础版免费,但有限制) | 免费 | 免费 | 免费 | 免费 |
监控 | 提供分发控制及监控 | 提供分发控制及监控 | no | no | no | no |
综上所述。我选择了sophix
开始傻瓜式接入:
1.首先去官网登陆创建应用(需要APP包名)
官网地址
2.Project-build.gradle
maven {
url "http://maven.aliyun.com/nexus/content/repositories/releases"
}
3.APP-build.gradle
compile 'com.aliyun.ams:alicloud-android-hotfix:3.2.8'
4.Mainfest-添加权限
//-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
// -- 外部存储读权限,调试工具加载本地补丁需要 6.0注意特殊处理 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
5.Mainfest-application 查看配置-见5.1
<meta-data
android:name="com.taobao.android.hotfix.IDSECRET"
android:value="AppKey" />
<meta-data
android:name="com.taobao.android.hotfix.APPSECRET"
android:value="AppSecret" />
<meta-data
android:name="com.taobao.android.hotfix.RSASECRET"
android:value="RSA" />
5.1应用配置查看
6.新建MyApplication,SophixStubApplication.
package com.example.duqianlong.sophixtest;
import android.app.Application;
/**
* Created by Duqianlong on 2019/8/22.
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//自己正常需要做的事情
}
}
package com.example.administrator.standardOA.application;
/**
* Created by Duqianlong on 2019/8/21.
*/
import android.content.Context;
import android.os.Looper;
import android.support.annotation.Keep;
import android.util.Log;
import android.widget.Toast;
import com.taobao.sophix.PatchStatus;
import com.taobao.sophix.SophixApplication;
import com.taobao.sophix.SophixEntry;
import com.taobao.sophix.SophixManager;
import com.taobao.sophix.listener.PatchLoadStatusListener;
/**
* Sophix入口类,专门用于初始化Sophix,不应包含任何业务逻辑。
* 此类必须继承自SophixApplication,onCreate方法不需要实现。
* 此类不应与项目中的其他类有任何互相调用的逻辑,必须完全做到隔离。
* AndroidManifest中设置application为此类,而SophixEntry中设为原先Application类。
* 注意原先Application里不需要再重复初始化Sophix,并且需要避免混淆原先Application类。
* 如有其它自定义改造,请咨询官方后妥善处理。
*/
public class SophixStubApplication extends SophixApplication {
private final String TAG = "SophixStubApplication";
// 此处SophixEntry应指定真正的Application,并且保证RealApplicationStub类名不被混淆。
@Keep
@SophixEntry(MyApplication.class)
static class RealApplicationStub {}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// 如果需要使用MultiDex,需要在此处调用。
// MultiDex.install(this);
initSophix();
}
private void initSophix() {
String appVersion = "0.0.0";
try {
appVersion = this.getPackageManager()
.getPackageInfo(this.getPackageName(), 0)
.versionName;
} catch (Exception e) {
}
final SophixManager instance = SophixManager.getInstance();
instance.setContext(this)
.setAppVersion(appVersion)
.setSecretMetaData(null, null, null)
.setEnableDebug(true)
.setEnableFullLog()
.setPatchLoadStatusStub(new PatchLoadStatusListener() {
@Override
public void onLoad(final int mode, final int code, final String info, final int handlePatchVersion) {
Log.e(TAG,"code="+code);
// 补丁加载回调通知
if (code == PatchStatus.CODE_LOAD_SUCCESS) {
// 表明补丁加载成功
// Toast.makeText(SophixStubApplication.this, "补丁加载成功!", Toast.LENGTH_SHORT).show();
Log.i(TAG, "sophix load patch success!");
Log.e(TAG, "Sophix加载补丁成功!");
} else if (code == PatchStatus.CODE_LOAD_RELAUNCH) {
Looper.prepare();
Toast.makeText(SophixStubApplication.this, "补丁预加载成功,重新启动生效!", Toast.LENGTH_SHORT).show();
Looper.loop();
// 表明新补丁生效需要重启. 开发者可提示用户或者强制重启;
// 建议: 用户可以监听进入后台事件, 然后调用killProcessSafely自杀,以此加快应用补丁,详见1.3.2.3
// 如果需要在后台重启,建议此处用SharePreference保存状态。
Log.i(TAG, "sophix preload patch success. restart app to make effect.");
Log.e(TAG, "Sophix预加载补丁成功。重新启动应用程序以生效。");
}else if (code==PatchStatus.CODE_REQ_NOUPDATE){
//查询阶段没有发现新补丁
}
}
}).initialize();
/**
* int CODE_LOAD_SUCCESS = 1;//加载阶段, 成功
int CODE_ERR_INBLACKLIST = 4;//加载阶段, 失败设备不支持
int CODE_REQ_NOUPDATE = 6;//查询阶段, 没有发布新补丁
int CODE_REQ_NOTNEWEST = 7;//查询阶段, 补丁不是最新的
int CODE_DOWNLOAD_SUCCESS = 9;//查询阶段, 补丁下载成功
int CODE_DOWNLOAD_BROKEN = 10;//查询阶段, 补丁文件损坏下载失败
int CODE_UNZIP_FAIL = 11;//查询阶段, 补丁解密失败
int CODE_LOAD_RELAUNCH = 12;//预加载阶段, 需要重启
int CODE_REQ_APPIDERR = 15;//查询阶段, appid异常
int CODE_REQ_SIGNERR = 16;//查询阶段, 签名异常
int CODE_REQ_UNAVAIABLE = 17;//查询阶段, 系统无效
int CODE_REQ_SYSTEMERR = 22;//查询阶段, 系统异常
int CODE_REQ_CLEARPATCH = 18;//查询阶段, 一键清除补丁
int CODE_PATCH_INVAILD = 20;//加载阶段, 补丁格式非法
//查询阶段的code说明
int CODE_QUERY_UNDEFINED = 31;//未定义异常
int CODE_QUERY_CONNECT = 32;//连接异常
int CODE_QUERY_STREAM = 33;//流异常
int CODE_QUERY_EMPTY = 34;//请求空异常
int CODE_QUERY_BROKEN = 35;//请求完整性校验失败异常
int CODE_QUERY_PARSE = 36;//请求解析异常
int CODE_QUERY_LACK = 37;//请求缺少必要参数异常
//预加载阶段的code说明
int CODE_PRELOAD_SUCCESS = 100;//预加载成功
int CODE_PRELOAD_UNDEFINED = 101;//未定义异常
int CODE_PRELOAD_HANDLE_DEX = 102;//dex加载异常
int CODE_PRELOAD_NOT_ZIP_FORMAT = 103;//基线dex非zip格式异常
int CODE_PRELOAD_REMOVE_BASEDEX = 105;//基线dex处理异常
//加载阶段的code说明 分三部分dex加载, resource加载, lib加载
//dex加载
int CODE_LOAD_UNDEFINED = 71;//未定义异常
int CODE_LOAD_AES_DECRYPT = 72;//aes对称解密异常
int CODE_LOAD_MFITEM = 73;//补丁SOPHIX.MF文件解析异常
int CODE_LOAD_COPY_FILE = 74;//补丁拷贝异常
int CODE_LOAD_SIGNATURE = 75;//补丁签名校验异常
int CODE_LOAD_SOPHIX_VERSION = 76;//补丁和补丁工具版本不一致异常
int CODE_LOAD_NOT_ZIP_FORMAT = 77;//补丁zip解析异常
int CODE_LOAD_DELETE_OPT = 80;//删除无效odex文件异常
int CODE_LOAD_HANDLE_DEX = 81;//加载dex异常
// 反射调用异常
int CODE_LOAD_FIND_CLASS = 82;
int CODE_LOAD_FIND_CONSTRUCTOR = 83;
int CODE_LOAD_FIND_METHOD = 84;
int CODE_LOAD_FIND_FIELD = 85;
int CODE_LOAD_ILLEGAL_ACCESS = 86;
//resource加载
public static final int CODE_LOAD_RES_ADDASSERTPATH = 123;//新增资源补丁包异常
//lib加载
int CODE_LOAD_LIB_UNDEFINED = 131;//未定义异常
int CODE_LOAD_LIB_CPUABIS = 132;//获取primaryCpuAbis异常
int CODE_LOAD_LIB_JSON = 133;//json格式异常
int CODE_LOAD_LIB_LOST = 134;//lib库不完整异常
int CODE_LOAD_LIB_UNZIP = 135;//解压异常
int CODE_LOAD_LIB_INJECT = 136;//注入异常
* */
}
@Override
public void onCreate() {
super.onCreate();
SophixManager.getInstance().queryAndLoadNewPatch();
}
}
6.1Mainfest-application
7.下载补丁生成工具以及测试工具
8.上传补丁包
8.1.本地测试补丁包
8.2.新建发布-灰度发布/全量发布
9.打开有问题的apk,开始热修复
如果生成补丁包的时候选择了强行使用冷启动,预下载补丁包后,需要重新启动生效。
10.修复结果
11.需要注意的
1.Windows下运行补丁生成工具,需要安装Java环境且在JDK7或以上才能正常使用。
2.android studio打包的时候需要关闭 Instant Run . 如图
3.如果同一个版本需要发布两个或者多个补丁包,第二次或者第N次生成补丁包的时候,旧包依旧用第一个版本的包。
4.Sophix不能修改AndroidManifest.xml文件,不能修改资源文件,不能新增四大组件,不能修改版本号,不能。所以Sophix更适合于修复致命bug。如果是增加或修改功能等操作,还是发布新版本比较好。
5.把RealApplicationStub的SophixEntry注解的内容改为自己原先真正的MyRealApplication类。
5.1:AndroidManifest里面的application改为新增的SophixStubApplication入口类。