一、前言:
1、简介:
深入探索Android热修复技术原理这本书主要讲解了Android的热修复中的热部署,冷部署以及资源和so库的修复技巧。全文主要讲Sophix应对以上四个方面的技术解析,不管是自家产品还是业界其他方案的横纵对比,Sophix技术目前都是最优的。
1、补丁小,合成不占太多空间和性能。
2、对代码的侵入小,对native代码的hook也精简,做到最大兼容。
3、支持的修复范围广。支持小范围的即时生效和大范围的冷启动。也支持so库和资源修复。
2、Sophix同时使用了热启动的底层替换方案及冷启动的类加载方案,两个方案使用的补丁是相同的。优先热启动。
二、阿里云注注册
1、首先去官网注册并登录账号,并进入控制台创建应用,获得相关的AppId,AppSecret,RSA密钥
2、如果刚注册,必须进行实名认证,才能使用。
3、进入控制台
三、集成阿里云热修复
1. 添加工程依赖
1、android studio集成方式
gradle远程仓库依赖, 打开项目找到app的build.gradle文件,添加如下配置:
添加maven仓库地址:
repositories {
maven {
url "http://maven.aliyun.com/nexus/content/repositories/releases"
}
}
2、添加gradle坐标版本依赖:
android {
......
defaultConfig {
applicationId "com.xxx.xxx" //包名
......
ndk {
//选择要添加的对应cpu类型的.so库。
//热修复支持五种
abiFilters 'arm64-v8a', 'armeabi', 'armeabi-v7a', 'x86', 'x86_64'
}
......
}
......
}
dependencies {
......
compile 'com.aliyun.ams:alicloud-android-hotfix:3.2.15'
......
}
2. 添加应用权限
Sophix SDK使用到以下权限,使用maven依赖或者aar依赖可以不用配置。具体配置在AndroidManifest.xml中。
READ_EXTERNAL_STORAGE权限属于Dangerous Permissions,仅调试工具获取外部补丁需要,不影响线上发布的补丁加载,调试时请自行做好android6.0以上的运行时权限获取。
3. 配置AndroidManifest文件
在AndroidManifest.xml
中间的application
节点下添加如下配置:
将上述value中的值分别改为通过平台HotFix服务申请得到的App Secret和RSA密钥,出于安全考虑,建议使用setSecretMetaData这个方法进行设置,详见SDK API的方法说明。如找不到对应参数,可参考EMAS快速入门>下载配置文件获取应用配置信息。
4. 混淆配置
#基线包使用,生成mapping.txt
-printmapping mapping.txt
#生成的mapping.txt在app/build/outputs/mapping/release路径下,移动到/app路径下
#修复后的项目使用,保证混淆结果一致
#-applymapping mapping.txt
#hotfix
-keep class com.taobao.sophix.**{*;}
-keep class com.ta.utdid2.device.**{*;}
-dontwarn com.alibaba.sdk.android.utils.**
#防止inline
-dontoptimize
5. 初始化
初始化的调用应该尽可能的早,必须在Application.attachBaseContext()的最开始(在super.attachBaseContext之后,如果有Multidex,也需要在Multidex.install之后)进行SDK初始化操作,初始化之前不能用到其他自定义类,否则极有可能导致崩溃。而查询服务器是否有可用补丁的操作可以在后面的任意地方。不建议在Application.onCreate()中初始化,因为如果带有ContentProvider,就会使得Sophix初始化时机太迟从而引发问题。
Sophix最新版本引入了新的初始化方式。
原来的初始化方式仍然可以使用。只是新方式可以提供更全面的功能修复支持,将会带来以下优点:
初始化与应用原先业务代码完全隔离,使得原先真正的Application可以修复,并且减少了补丁预加载时间等等。
腾讯的Application (SophixStubApplication )
package com.my.pkg;
import android.app.Application;
import android.content.Context;
import android.support.annotation.Keep;
import android.util.Log;
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;
import com.my.pkg.MyRealApplication;
/**
* 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(BaseApplication .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) {
if (code == PatchStatus.CODE_LOAD_SUCCESS) {
Log.i(TAG, "sophix load patch success!");
} else if (code == PatchStatus.CODE_LOAD_RELAUNCH) {
// 如果需要在后台重启,建议此处用SharePreference保存状态。
Log.i(TAG, "sophix preload patch success. restart app to make effect.");
}
}
}).initialize();
}
}
自己原有的Application
public class BaseApplication extends Application {
private static Context mInstance;
private static String uniqueID;
private final String TAG = "BaseApplication";
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
//查询下载补丁包
SophixManager.getInstance().queryAndLoadNewPatch();
initData();
}
}
注意:获取版本号,开始下载安装补丁包(可以放在自己的Application中的onCreate()中)
// queryAndLoadNewPatch不可放在attachBaseContext 中,否则无网络权限,建议放在后面任意时刻,如onCreate中
SophixManager.getInstance().queryAndLoadNewPatch();
这其中,关键一点是:
@Keep
@SophixEntry(BaseApplication .class)
static class RealApplicationStub {}
SophixEntry应指定项目中原先真正的Application(原项目里application的android::name指定的),这里用BaseApplication 指代。并且保证BaseApplication Stub类名不被混淆。而SophixStubApplication的类名和包名可以自行取名。
这里的Keep是android.support包中的类,目的是为了防止这个内部静态类的类名被混淆,因为sophix内部会反射获取这个类的SophixEntry。如果项目中没有依赖android.support的话,就需要在progurad里面手动指定RealApplicationStub不被混淆,详见下文。
然后,在proguard文件里面需要加上下面内容:
-keepclassmembers class com.my.pkg.BaseApplication {
public ();
}
-keep class com.my.pkg.SophixStubApplication$BaseApplication Stub
目的是防止真正Application的构造方法被proguard混淆。
最后,需要把AndroidManifest里面的application改为这个新增的SophixStubApplication类:
... ...
这样便完成了新方式的初始化接入改造。
6、总结一下,过程一共有四个步骤:
1、把此SophixStubApplication入口类添加进项目中,所有Sophix相关初始化放在此类中。并且不应包含开发者的任何业务逻辑代码。 若使用了MultiDex,也应在SophixStubApplication的initSophix之前添加,并且需要记得在原来的Application里面去除MultiDex,避免重复调用导致问题。
2、 把RealApplicationStub的SophixEntry注解的内容改为自己原先真正的MyRealApplication类。
3、混淆文件中确保某些内容不被混淆。
4、AndroidManifest里面的application改为新增的SophixStubApplication入口类。
7、常见状态码:
一个补丁的加载一般分为三个阶段: 查询、预加载、加载,各个阶段的常见状态码如下:
//兼容老版本的code说明
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_REQ_TOOFAST = 19;//连续两次请求不能小于3s
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;//注入异常
四、生成补丁包
1、下载打包工具:
Mac版本打包工具地址:http://ams-hotfix-repo.oss-cn-shanghai.aliyuncs.com/SophixPatchTool_macos.zip
Windows版本打包工具地址:http://ams-hotfix-repo.oss-cn-shanghai.aliyuncs.com/SophixPatchTool_windows.zip
Linux版本打包工具地址:http://ams-hotfix-repo.oss-cn-shanghai.aliyuncs.com/SophixPatchTool_linux.zip
2、 生成补丁包
1、点击软件首次打开
2、点击设置,然后配置签名信息,指定补丁包的输出路径(只第一次需要配置这些信息)
3、直接选择旧包和新包(旧包指有bug的包,新包是修复完bug的包)
然后点击 GO!
补丁包就会生成在你指定的输出路径下:
4、然后进入控制台,添加版本,上传补丁。
注意,版本号必须和项目保持一致。
// 默认版本号是 1.0,会获取当前版本
String appVersion;
try {
appVersion = this.getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName;
} catch (Exception e) {
appVersion = "1.0.0";
}
SophixManager.getInstance().setContext(this)
.setAppVersion(appVersion)
项目中的版本号( build.gradle 中的 versionName )
defaultConfig {
applicationId "com.risecenter.rise_online_android"
minSdkVersion 21
targetSdkVersion 26
versionCode 1
versionName "1.0.0"
}
补丁后台的版本也要一致:
依次进行下列步骤即可:
灰度发布:可指定修复补丁的手机数量
全量发布:用于生产环境。经本地测试,灰度发布测试没问题后,就可以全量发布了
特别注意:发布完毕,杀掉进程(而不是返回)才会生效。
- 链接:https://www.jianshu.com/p/143a2e08fee9
- 链接:https://blog.csdn.net/qq_33337504/article/details/77864348
- 链接:https://help.aliyun.com/document_detail/180957.html?spm=a2c4g.11186623.6.574.59eec464rgkFsH