阿里最新热修复框架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"
    ... ...>
    ... ...

sample源码


生成、上传、调试补丁

下载打包工具:
patch补丁包生成需要使用到打补丁工具SophixPatchTool, 如还未下载打包工具,请前往下载Android打包工具。

该工具提供了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才会重新下载.

· 在上传新的补丁之后,要调试时,如果以往的补丁有处于 已灰度已发布状态,要停止发布。 如果不停止,最新的补丁处于等待中,也就是未发布。那么当你打开客户端,它会拉取以往发布的补丁修复程序,这样会影响你观测调试结果。

· 后台数据可能有少许延迟。

  • 停止发布
    点击【停止发布】后,用户选择停止发布后,系统将停止该补丁的继续发布,但已加载该补丁的设备会依然保持安装该补丁的状态。

界面变成:


停止发布 后
  • 继续发布
    用户点击【继续发布】后,将可以重新设置发布规则。

如果当前版本在停止前处于灰度中,继续发布可以:

· 重设灰度发布规则,新的规则中设备数必须大于之前的值。
· 改为全量发布。

灰度状态下继续发布

所以,从灰度发布到全量发布的步骤是

· 先在补丁详情页勾选灰度发布,点击确认发布
· 推送完所有灰度设备后,点击停止发布
· 再点击继续发布,弹出框里选择全量发布

如果当前版本在停止前处于全量发布,继续发布可以:

继续全量发布。 --- 对,你没看错,就是逗你玩!

  • 选择回滚
    用户选择回滚的目标补丁后,所有该应用版本下的设备都会回滚到目标补丁的版本。

使用回滚功能必需要具备一下几个条件:

· 当前的版本已停止发布。
· 该版本之前存在至少一个全量发布的历史版本。

全量发布

选择全量发布后,将对所有安装了当前应用版本(即之前创建应用时所填写的应用版本号)的设备推送该补丁。

与灰度发布类似,在全量发布会可以根据自身需要停止本次全量发布,停止发布后可以选择:

· 继续全量发布。
· 回滚版本(如果存在历史版本)

添加过滤机型

全量发布后,我们可以添加过滤机型。
不全量发布是不可以添加机型过滤的

image.png

在App版本详情页,点击【添加过滤机型】
点击添加过滤机型弹出框

这里对过滤机型的弹出框参数进行说明:

  • 系统版本
    系统版本是指手机所使用的OS的版本。

在控制台中,有相应的系统版本列表可供选择。如果列表中没有需要自定义,请按如下标准获取系统版本。

android.os.Build.VERSION.RELEASE
例如系统版本结果是:7.1

  • 手机品牌
    手机品牌是指手机贴牌商标代表的品牌,需要区别手机制造商,手机制造商可能会生产多个品牌,一个品牌也可能是多个制造商生产。

在控制台中,我们有相应的品牌列表供选择使用。如果需要自定义,请按如下标准获取手机品牌,注意实际过滤时不区分大小写。

android.os.Build.BRAND
例如手机品牌是:Xiaomi

  • 手机机型
    手机机型是指某个手机品牌下手机具体的型号。

目前由于手机机型庞杂,没有提供选择列表供选择,后续会支持。填写手机机型时请按如下标准,不区分大小写。

android.os.Build.MODEL
例如手机型号是:OPPO R11

【注意】如果想设置全部机型,请在自定义机型里面,输入 :all
(就是 冒号+all)

到这里,sophix集成的全部内容就结束了。阿里热修复官方的文档有点琐碎,我把重点和注意点都挑出来了。读完这四篇,相信你会迅速集成sophix到自己的应用里。

这再给出官方接入文档地址,给还想看官方文档的朋友。官方接入文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容