阿里Sophix 首次接入

一、前言:

1、简介:

深入探索Android热修复技术原理这本书主要讲解了Android的热修复中的热部署,冷部署以及资源和so库的修复技巧。全文主要讲Sophix应对以上四个方面的技术解析,不管是自家产品还是业界其他方案的横纵对比,Sophix技术目前都是最优的。

1、补丁小,合成不占太多空间和性能。
2、对代码的侵入小,对native代码的hook也精简,做到最大兼容。
3、支持的修复范围广。支持小范围的即时生效和大范围的冷启动。也支持so库和资源修复。

2、Sophix同时使用了热启动的底层替换方案及冷启动的类加载方案,两个方案使用的补丁是相同的。优先热启动。

图片.png

二、阿里云注注册

1、首先去官网注册并登录账号,并进入控制台创建应用,获得相关的AppId,AppSecret,RSA密钥

图片.png

2、如果刚注册,必须进行实名认证,才能使用。

图片.png

3、进入控制台

图片.png

三、集成阿里云热修复

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中。

    <! -- 网络权限 -->
    <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以上的运行时权限获取。

3. 配置AndroidManifest文件

AndroidManifest.xml中间的application节点下添加如下配置:

    <meta-data
    android:name="com.taobao.android.hotfix.IDSECRET"
    android:value="App ID" />
    <meta-data
    android:name="com.taobao.android.hotfix.APPSECRET"
    android:value="App Secret" />
    <meta-data
    android:name="com.taobao.android.hotfix.RSASECRET"
    android:value="RSA密钥" />

将上述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 <init>();
    }
    -keep class com.my.pkg.SophixStubApplication$BaseApplication Stub

目的是防止真正Application的构造方法被proguard混淆。

最后,需要把AndroidManifest里面的application改为这个新增的SophixStubApplication类:

        <application
            android:name="com.my.pkg.BaseApplication "
            ... ...>
            ... ...

这样便完成了新方式的初始化接入改造。

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、点击软件首次打开


图片.png

2、点击设置,然后配置签名信息,指定补丁包的输出路径(只第一次需要配置这些信息)


图片.png

3、直接选择旧包和新包(旧包指有bug的包,新包是修复完bug的包)
然后点击 GO!
补丁包就会生成在你指定的输出路径下:


图片.png
图片.png

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"
    }

补丁后台的版本也要一致:


图片.png

依次进行下列步骤即可:


图片.png
图片.png
图片.png

灰度发布:可指定修复补丁的手机数量
全量发布:用于生产环境。经本地测试,灰度发布测试没问题后,就可以全量发布了

特别注意:发布完毕,杀掉进程(而不是返回)才会生效。


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