阿里最新热修复框架sophix-3.1.6集成详解
*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
本文更新于2017年11月20日
前言
关于sophix集成和使用,网上有了很多前辈写的博客。读了很多,感觉都不太详细和系统。所以自己尝试写sophix集成文章,本文包括四部分内容:
- 控制台开通移动热修复
- 工程代码快速接入
- 生成、上传、调试补丁
- 补丁灰度发布、全量发布、机型过滤
关于sophix的原理和与其他热修复框架的比较,戳官方文档
阿里手淘团队出书了,业界首部全方位系统介绍热修复原理书籍,从阿里Sophix方案开发过程入手权威解读!《深入探索Android热修复技术原理》
这本书建议读一读。
话不多说,集成开始:
控制台开通移动热修复
阿里云控制台的使用有点绕,要注意了,对照着一步一步来
- 登录阿里云,开通移动热修复
Ps:
image.png如果自己进了阿里云官网首页,怎么找热修复: 鼠标滑到 菜单栏 【产品】,弹出的菜单,找到白色字体类别【移动云】,移动云 的子菜单里找到【移动热修复】
· 右上角登录,可以使用淘宝账号直接登录。注册一个也行。
· 左边 点击 立即开通。
没开通的,会跳转到一个页面,告知 【确认开通】。
确认开通后,跳转到控制台的移动热修复页面,酱紫的
移动热修复 控制台Ps:
如果读者自己是通过点官网首页左上角的【控制台】,直接进入了【管理控制台】,那怎么进到移动热修复的控制台页面呢:看上面的截图,菜单栏的 【产品与服务】,是以首字母排列的。找Y类-【移动热修复】。点一下,就切换到移动热修复的管理了。
截图中 【创建App】是新开一个标签页,跳转到 [移动云] 控制台(Mobile Hub)去创建的,和当前处在的 [移动热修复] 控制台 不同,不要搞混。
- 点击【创建App】,会提示先【创建产品】
产品下包含着 创建应用(App),产品的名字随便起。
- 点击 蓝色字体产品名称 或 【管理】,进入 产品信息页。
Ps:
在本页的 应用列表的App都有 查看信息 选项,这里用不到它,因为没有我们需要的RSA密钥。
点击 【创建应用】,填入App名(最好和项目名称一致),应用类型 选 Android,填入packageName。 (bundleId是iOS的标识)
创建成功后,在下方的应用列表展示信息。
- 点击 移动热修复,再点击应用列表 对应App 的【管理】,查看 AppId、AppSecret、RSA密钥
进入移动热修复有两种方法:
1.看上图,可以在当前移动云 产品信息页 ,点击 移动热修复标签,
2.可以关掉当前网页(还记得在移动热修复控制台【创建App】是新开一个标签页吗)这样也可以回到移动热修复的页面,再刷新一下。
第1种方法结果:
第1种方法结果
第2种方法结果:
第2种方法,图一
点击应用列表【管理】,进入图二
第2种方法,图二
总之,一定要在创建完产品和应用后,到 [移动热修复] 标签页,才能查看到AppId,AppSecret,RSA密钥。不要在移动云的产品处查看,那样你是看不到RSA密钥的。
关于 【管理控制台】 的更多使用详情, 戳这里
工程代码快速接入
- studio添加依赖:
gradle远程仓库依赖, 打开项目找到app的build.gradle文件,添加如下配置:
添加maven仓库地址:
repositories {
maven {
url "http://maven.aliyun.com/nexus/content/repositories/releases"
}
}
添加gradle坐标版本依赖:
compile 'com.aliyun.ams:alicloud-android-hotfix:3.1.6'
-
配置AndroidManifest文件
-
需要用到一下权限:
<! -- 网络权限 --> <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" /> <! -- 外部存储读权限,调试工具加载本地补丁需要 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
READ_EXTERNAL_STORAGE
权限属于Dangerous Permissions,仅调试工具获取外部补丁需要,不影响线上发布的补丁加载,调试时请自行做好android6.0以上的运行时权限获取。 -
application节点下添加如下配置:添加AppId,AppSecret,RSA密钥
<meta-data android:name="com.taobao.android.hotfix.IDSECRET" android:value="AppId" /> <meta-data android:name="com.taobao.android.hotfix.APPSECRET" android:value="AppSecret" /> <meta-data android:name="com.taobao.android.hotfix.RSASECRET" android:value="RSA密钥" />
因为AppSecret和RSA密钥比较敏感,出于安全考虑,可以在代码中通过setSecretMetaData这个方法进行设置。这个下面写Java代码时再说。
-
-
混淆配置
#基线包使用,生成mapping.txt
-printmapping mapping.txt
#生成的mapping.txt在app/buidl/outputs/mapping/release路径下,移动到/app路径下
#修复后的项目使用,保证混淆结果一致
#-applymapping mapping.txt
#hotfix
-keep class com.taobao.sophix.**{*;}
-keep class com.ta.utdid2.device.**{*;}
#防止inline
-dontoptimize
- Java代码初始化接入
Sophix 3.1.6版本以后引入了新的初始化方式。
原来的初始化方式仍然可以使用,不过新方式将会带来以下优点:初始化与应用原先业务代码完全隔离,使得原先真正的Application可以修复,并且减少了补丁预加载时间。而且,新方式已经优先支持Android 8.0版本。
本文使用这种新型方式。
1- 导入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,也就是你的应用中原有的主Application,并且保证RealApplicationStub类名不被混淆。
@Keep
@SophixEntry(MyRealApplication.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) //三个参数分别对应AndroidManifest里面的AppId、AppSecret、RSA密钥,可以不在AndroidManifest设置而是用此函数来设置Secret。放到代码里面进行设置可以自定义混淆代码,更加安全,此函数的设置会覆盖AndroidManifest里面的设置,如果对应的值设为null,默认会在使用AndroidManifest里面的。
.setEnableDebug(true)//默认为false,设为true即调试模式下会输出日志以及不进行补丁签名校验. 线下调试此参数可以设置为true, 它会强制不对补丁进行签名校验, 所有就算补丁未签名或者签名失败也发现可以加载成功. 但是正式发布该参数必须为false, false会对补丁做签名校验, 否则就可能存在安全漏洞风险。
.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.");
/** 不可以直接Process.killProcess(Process.myPid())来杀进程,这样会扰乱Sophix的内部状态。
* 因此如果需要杀死进程,建议使用这个方法,它在内部做一些适当处理后才杀死本进程。*/
instance.killProcessSafely();
}
}
}).initialize();
}
@Override
public void onCreate() {
super.onCreate();
// queryAndLoadNewPatch不可放在attachBaseContext 中,否则无网络权限,建议放在后面任意时刻,如onCreate中
SophixManager.getInstance().queryAndLoadNewPatch();
/** 补丁在后台发布之后, 并不会主动下行推送到客户端, 客户端通过调用queryAndLoadNewPatch方法查询后台补丁是否可用*/
}
}
初始化sophix务必放在attachBaseContext中,onCreate不需要自行实现。同时自定义的SophixStubApplication需要继承com.taobao.sophix.SophixApplication。
这其中,关键一点是:
@Keep
@SophixEntry(MyRealApplication.class)
static class RealApplicationStub {}
SophixEntry应指定项目中原先真正的Application(原项目里application的android::name指定的),这里用MyRealApplication指代。并且保证RealApplicationStub类名不被混淆。而SophixStubApplication的类名和包名可以自行取名。
这里的Keep是android.support包中的类,目的是为了防止这个内部静态类的类名被混淆,因为sophix内部会反射获取这个类的SophixEntry。如果项目中没有依赖android.support的话,就需要在progurad里面手动指定RealApplicationStub不被混淆。
2- 然后,在proguard文件里面需要加上下面内容:
-keepclassmembers class com.my.pkg.MyRealApplication {
public <init>();
}
# 如果不使用android.support.annotation.Keep则需加上此行
# -keep class com.my.pkg.SophixStubApplication$RealApplicationStub
目的是防止真正Application的构造方法被proguard混淆。
最后,需要把AndroidManifest里面的application改为这个新增的SophixStubApplication类:
<application
android:name="com.my.pkg.SophixStubApplication"
... ...>
... ...
生成、上传、调试补丁
下载打包工具:
patch补丁包生成需要使用到打补丁工具SophixPatchTool, 如还未下载打包工具,请前往下载Android打包工具。
-
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
该工具提供了Windows和macOS和Linux版本,Windows下运行SophixPatchTool.exe,macOS下运行SophixPatchTool.app,Linux下(Ubuntu 16.04 64bit最佳)运行SophixPatchTool。并且需要安装Java环境且在JDK7或以上才能正常使用。
我是先 生成调试包,有问题的程序,Build apk,改名字为旧包.apk。然后修复完,再Build apk,改名字为新包.apk。这样能看Log。测试成功后,再生成发布包,再测试一遍。
补丁打包工具主对话框- 旧包:<必填> 有问题的APK。
- 新包:<必填> 修复过该问题APK。
- 日志:打开日志输出窗口。
- 高级:展开高级选项。
- 设置:补丁输出路径和签名文件设置。
- GO!:开始生成补丁。
点击【高级】,弹出 补丁和签名设置
image.png
- 强制冷启动:勾选的话强制生成补丁包为需要冷启动才能修复的格式。默认不选的话,工具会根据代码变更情况自动选择即时热替换或者冷启动修复。
- 不比较资源:打补丁时不比较资源的变化。
- 不比较SO库:打补丁时不比较SO库的变化。
所以,高级选项可以不做处理。
强制冷启动:勾选的话强制生成补丁包为需要冷启动才能修复的格式。默认不选的话,工具会根据代码变更情况自动选择即时热替换或者冷启动修复。
不比较资源:打补丁时不比较资源的变化。
不比较SO库:打补丁时不比较SO库的变化。
点击【设置】
image.png
- 补丁输出路径:<必填> 指定生成补丁之后补丁的存放位置,必须是已存在的目录。
- Key Store Path:<选填>本地的签名文件的路径,不输入则不做签名。
- Key Store Password:<选填>证书文件的密码。
- Key Alias:<选填>Key的别名。
- Key Passwrod:<选填>Key的密码。
下面的一般不做处理: - AES Key:<选填>自定义aes秘钥, 必须是16位数字或字母的组合。必须与setAesKey中设置的秘钥一致。
- Filter Class File:<选填>本地的白名单类列表文件的路径,放进去的类不会再计算patch,文件格式: 一行一个类名。
Ps:
mac下的补丁工具若出现一打开就崩溃的情况,请将补丁工具移到“应用程序”目录下即可。
点击 Go ,生成的补丁如下图:
补丁
补丁文件名必须为:sophix-patch.jar。不能更改。
上传补丁
-
首先进入 移动热修复 管理控制台
移动热修复 管理控制台 -
点击App列表里的操作-【管理】,进入详情页
App详情页 -
点击 【添加版本】,也就是应用的版本号
这里的版本号一定要和工程里的gradle文件里记录的一致。我截图上的一个1.0和1.0.0。搞1.0.0测试了半天,没结果,傻不傻。gradle里默认是“1.0” -
添加完版本,点击应用版本列表下的 【查看详情】,进入版本详情页
版本详情页
点击 【上传补丁】,补丁版本列表更新。
-
点击 补丁版本列表 的 【查看详情】,进入 补丁详情页,可以查看补丁属性和补丁状态
补丁详情页
上图有个二维码,在正式发布前,我们用测试工具扫码测试下。
测试工具是个apk。它是通过扫描补丁二维码,下载到手机上,然后通过在apk界面上输入你要测试的应用包名,将补丁打到应用里。
调试补丁
调试工具App地址:http://ams-hotfix-repo.oss-cn-shanghai.aliyuncs.com/hotfix_debug_tool-release.apk
调试工具App界面那个截图上的 【断开连接应用】,最开始是 【连接应用】
- 先把你的有bug的apk安装到手机上
- 然后打开该调试工具App,先输入 bug应用 包名,点 【连接应用】
- 点 【扫描二维码】,扫 网页上 补丁详情页 的二维码
接下来,就不用管了。它会下载补丁,并打到应用上。
看到调试App界面是输出信息,有以下几条,就代表成功了。
app connect successful.
patch download success.
please restart app to reload new patch as exist old patch.
打开你的bug应用,就可以看到变化了。
来个截图示例,应用源码就是文章前面给的sample
sophix调试补丁.gif
SophixTest原来只显示个 helloworld,经过Sophix调试工具V3的打补丁后,再次打开SophixTest就变成了有福利字样,并显示张美女图片。
补丁灰度发布、全量发布、机型过滤
注意事项:
- 支持多渠道包仅选用某个渠道包的补丁,只需要保证变化相同即可,不过对于不同的apk包最好进行全面的测试。
- 发布前请严格按照:扫码内测 => 灰度发布 => 全量发布的流程进行,以保证补丁包能够正常在所有Android版本的机型上生效。
- 补丁状态:
- 等待中:补丁上传成功,等待操作。
- 已灰度:补丁正在进行灰度发布。
- 已发布:补丁已全量发布至所有设备。
- 已停止:补丁发布行为已暂停。
灰度发布
在应用版本详情页,点击补丁版本列表里的【查看详情】,进入 补丁详情页。
补丁详情页
在刚刚上传完补丁后,补丁处于 等待中 的状态,勾选 灰度发布。
设置完设备数,客户端拉取补丁会消耗该设备数,达到灰度设备数后,灰度补丁自动置为停止状态。
设备数:指设备请求更新该补丁的次数,并不等于绝对设备数。
例如:1个设备请求了2次更新该补丁,则会消耗掉2的设备数。
-
确认发布
点击【确认发布】,补丁状态为 已灰度 ,进入灰度发布状态。
灰度发布状态
这时,当用户打开客户端,就会拉取线上的补丁,修复程序。
还记得代码中的queryAndLoadNewPatch()方法吗,它的作用去看sample源码注释。
- 成功推送设备数:每当有设备发起一次更新请求,且补丁下载成功,则记为一次成功推送。
- 累计加载设备数:每当有设备成功加载该补丁,则记为一次累计加载。
注:
· 只会下载补丁版本号比当前应用存在的补丁版本号高的补丁, 比如当前应用已经下载了补丁版本号为5的补丁, 那么只有后台发布的补丁版本号>5才会重新下载.
· 在上传新的补丁之后,要调试时,如果以往的补丁有处于 已灰度 或已发布状态,要停止发布。 如果不停止,最新的补丁处于等待中,也就是未发布。那么当你打开客户端,它会拉取以往发布的补丁修复程序,这样会影响你观测调试结果。
· 后台数据可能有少许延迟。
- 停止发布
点击【停止发布】后,用户选择停止发布后,系统将停止该补丁的继续发布,但已加载该补丁的设备会依然保持安装该补丁的状态。
界面变成:
停止发布 后
- 继续发布
用户点击【继续发布】后,将可以重新设置发布规则。
如果当前版本在停止前处于灰度中,继续发布可以:
灰度状态下继续发布· 重设灰度发布规则,新的规则中设备数必须大于之前的值。
· 改为全量发布。
所以,从灰度发布到全量发布的步骤是
· 先在补丁详情页勾选灰度发布,点击确认发布
· 推送完所有灰度设备后,点击停止发布
· 再点击继续发布,弹出框里选择全量发布
如果当前版本在停止前处于全量发布,继续发布可以:
继续全量发布。 --- 对,你没看错,就是逗你玩!
- 选择回滚
用户选择回滚的目标补丁后,所有该应用版本下的设备都会回滚到目标补丁的版本。
使用回滚功能必需要具备一下几个条件:
· 当前的版本已停止发布。
· 该版本之前存在至少一个全量发布的历史版本。
全量发布
选择全量发布后,将对所有安装了当前应用版本(即之前创建应用时所填写的应用版本号)的设备推送该补丁。
与灰度发布类似,在全量发布会可以根据自身需要停止本次全量发布,停止发布后可以选择:
· 继续全量发布。
· 回滚版本(如果存在历史版本)
添加过滤机型
全量发布后,我们可以添加过滤机型。
不全量发布是不可以添加机型过滤的
在App版本详情页,点击【添加过滤机型】
点击添加过滤机型弹出框
这里对过滤机型的弹出框参数进行说明:
- 系统版本
系统版本是指手机所使用的OS的版本。
在控制台中,有相应的系统版本列表可供选择。如果列表中没有需要自定义,请按如下标准获取系统版本。
android.os.Build.VERSION.RELEASE
例如系统版本结果是:7.1
- 手机品牌
手机品牌是指手机贴牌商标代表的品牌,需要区别手机制造商,手机制造商可能会生产多个品牌,一个品牌也可能是多个制造商生产。
在控制台中,我们有相应的品牌列表供选择使用。如果需要自定义,请按如下标准获取手机品牌,注意实际过滤时不区分大小写。
android.os.Build.BRAND
例如手机品牌是:Xiaomi
- 手机机型
手机机型是指某个手机品牌下手机具体的型号。
目前由于手机机型庞杂,没有提供选择列表供选择,后续会支持。填写手机机型时请按如下标准,不区分大小写。
android.os.Build.MODEL
例如手机型号是:OPPO R11
【注意】如果想设置全部机型,请在自定义机型里面,输入 :all
(就是 冒号+all)
到这里,sophix集成的全部内容就结束了。阿里热修复官方的文档有点琐碎,我把重点和注意点都挑出来了。读完这四篇,相信你会迅速集成sophix到自己的应用里。
这再给出官方接入文档地址,给还想看官方文档的朋友。官方接入文档