美文网首页AndroidAndroid 热修复Android
阿里最新热修复框架sophix-3.1.6集成详解

阿里最新热修复框架sophix-3.1.6集成详解

作者: 月下溪明 | 来源:发表于2017-11-23 16:58 被阅读317次

    *本篇文章已授权微信公众号 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到自己的应用里。

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

    相关文章

      网友评论

      • mapleeeeee:请问如何跟美团的walle一起使用呢?包括发布,修复等
        mapleeeeee:@月下溪明 老铁如果配合上walle就完美了:grin:
        月下溪明:@mapleeeeee 没用过walle 哈哈
      • 绳_:大佬 我练习时出现
        java.lang.IllegalAccessError: Field '....SpleashActivity.viewPager' is inaccessible to class '....SpleashActivity$2' (declaration of '....SpleashActivity$2' appears in /data/user/0/.../files/sophix/patch_/sophix-patch.jar)

        这句((View)viewPager.getParent()).setBackgroundColor(evaluate)
        报错说方法不在dex分包中怎么办
        月下溪明:@绳_98ea 解决了没有?抱歉工作关系很长时间没上简书了。还没解决的话,最好你把你的project发给我,这是我的邮箱renzhicheng00@163.com。然后在邮件里留言你具体的操作和报错信息。我解决后回复给你
        绳_::sweat: 我拿一个viewpager滑动position实时改变背景色的demo直接练习sophix ,如果吧evaluate资源改了就会报错,不改就成功修复不报错。

      本文标题:阿里最新热修复框架sophix-3.1.6集成详解

      本文链接:https://www.haomeiwen.com/subject/dtnzvxtx.html