阿里Sophix 首次接入

一、前言:

1、簡介:

深入探索Android熱修復(fù)技術(shù)原理這本書主要講解了Android的熱修復(fù)中的熱部署,冷部署以及資源和so庫的修復(fù)技巧。全文主要講Sophix應(yīng)對以上四個方面的技術(shù)解析,不管是自家產(chǎn)品還是業(yè)界其他方案的橫縱對比,Sophix技術(shù)目前都是最優(yōu)的。

1、補丁小,合成不占太多空間和性能。
2、對代碼的侵入小,對native代碼的hook也精簡,做到最大兼容。
3、支持的修復(fù)范圍廣。支持小范圍的即時生效和大范圍的冷啟動。也支持so庫和資源修復(fù)。

2、Sophix同時使用了熱啟動的底層替換方案及冷啟動的類加載方案,兩個方案使用的補丁是相同的。優(yōu)先熱啟動。

圖片.png

二、阿里云注注冊

1、首先去官網(wǎng)注冊并登錄賬號,并進入控制臺創(chuàng)建應(yīng)用,獲得相關(guān)的AppId,AppSecret,RSA密鑰

圖片.png

2、如果剛注冊,必須進行實名認(rèn)證,才能使用。

圖片.png

3、進入控制臺

圖片.png

三、集成阿里云熱修復(fù)

1. 添加工程依賴

1、android studio集成方式

gradle遠程倉庫依賴, 打開項目找到app的build.gradle文件,添加如下配置:

添加maven倉庫地址:

    repositories {
       maven {
           url "http://maven.aliyun.com/nexus/content/repositories/releases"
       }
    }

2、添加gradle坐標(biāo)版本依賴:

    android {
        ......
        defaultConfig {
            applicationId "com.xxx.xxx" //包名
            ......
            ndk {
                //選擇要添加的對應(yīng)cpu類型的.so庫。
                //熱修復(fù)支持五種
                abiFilters 'arm64-v8a', 'armeabi', 'armeabi-v7a', 'x86', 'x86_64'
            }
            ......
        }
        ......
    }
    dependencies {
        ......
            compile 'com.aliyun.ams:alicloud-android-hotfix:3.2.15'
        ......
    }

2. 添加應(yīng)用權(quán)限

Sophix SDK使用到以下權(quán)限,使用maven依賴或者aar依賴可以不用配置。具體配置在AndroidManifest.xml中。

    <! -- 網(wǎng)絡(luò)權(quán)限 -->
    <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" />
    <! -- 外部存儲讀權(quán)限,調(diào)試工具加載本地補丁需要 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

READ_EXTERNAL_STORAGE權(quán)限屬于Dangerous Permissions,僅調(diào)試工具獲取外部補丁需要,不影響線上發(fā)布的補丁加載,調(diào)試時請自行做好android6.0以上的運行時權(quán)限獲取。

3. 配置AndroidManifest文件

AndroidManifest.xml中間的application節(jié)點下添加如下配置:

    <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服務(wù)申請得到的App Secret和RSA密鑰,出于安全考慮,建議使用setSecretMetaData這個方法進行設(shè)置,詳見SDK API的方法說明。如找不到對應(yīng)參數(shù),可參考EMAS快速入門>下載配置文件獲取應(yīng)用配置信息。

4. 混淆配置

    #基線包使用,生成mapping.txt
    -printmapping mapping.txt
    #生成的mapping.txt在app/build/outputs/mapping/release路徑下,移動到/app路徑下
    #修復(fù)后的項目使用,保證混淆結(jié)果一致
    #-applymapping mapping.txt
    #hotfix
    -keep class com.taobao.sophix.**{*;}
    -keep class com.ta.utdid2.device.**{*;}
    -dontwarn com.alibaba.sdk.android.utils.**
    #防止inline
    -dontoptimize

5. 初始化

初始化的調(diào)用應(yīng)該盡可能的早,必須在Application.attachBaseContext()的最開始(在super.attachBaseContext之后,如果有Multidex,也需要在Multidex.install之后)進行SDK初始化操作,初始化之前不能用到其他自定義類,否則極有可能導(dǎo)致崩潰。而查詢服務(wù)器是否有可用補丁的操作可以在后面的任意地方。不建議在Application.onCreate()中初始化,因為如果帶有ContentProvider,就會使得Sophix初始化時機太遲從而引發(fā)問題。

Sophix最新版本引入了新的初始化方式。

原來的初始化方式仍然可以使用。只是新方式可以提供更全面的功能修復(fù)支持,將會帶來以下優(yōu)點:

初始化與應(yīng)用原先業(yè)務(wù)代碼完全隔離,使得原先真正的Application可以修復(fù),并且減少了補丁預(yù)加載時間等等。

騰訊的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,不應(yīng)包含任何業(yè)務(wù)邏輯。
     * 此類必須繼承自SophixApplication,onCreate方法不需要實現(xiàn)。
     * 此類不應(yīng)與項目中的其他類有任何互相調(diào)用的邏輯,必須完全做到隔離。
     * AndroidManifest中設(shè)置application為此類,而SophixEntry中設(shè)為原先Application類。
     * 注意原先Application里不需要再重復(fù)初始化Sophix,并且需要避免混淆原先Application類。
     * 如有其它自定義改造,請咨詢官方后妥善處理。
     */
    public class SophixStubApplication extends SophixApplication {
        private final String TAG = "SophixStubApplication";
        // 此處SophixEntry應(yīng)指定真正的Application,并且保證RealApplicationStub類名不被混淆。
        @Keep
        @SophixEntry(BaseApplication .class)
        static class RealApplicationStub {}
        @Override
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(base);
    //         如果需要使用MultiDex,需要在此處調(diào)用。
    //         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保存狀態(tài)。
                                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 中,否則無網(wǎng)絡(luò)權(quán)限,建議放在后面任意時刻,如onCreate中
    SophixManager.getInstance().queryAndLoadNewPatch();

這其中,關(guān)鍵一點是:

        @Keep
        @SophixEntry(BaseApplication .class)
        static class RealApplicationStub {}

SophixEntry應(yīng)指定項目中原先真正的Application(原項目里application的android::name指定的),這里用BaseApplication 指代。并且保證BaseApplication Stub類名不被混淆。而SophixStubApplication的類名和包名可以自行取名。

這里的Keep是android.support包中的類,目的是為了防止這個內(nèi)部靜態(tài)類的類名被混淆,因為sophix內(nèi)部會反射獲取這個類的SophixEntry。如果項目中沒有依賴android.support的話,就需要在progurad里面手動指定RealApplicationStub不被混淆,詳見下文。

然后,在proguard文件里面需要加上下面內(nèi)容:

    -keepclassmembers class com.my.pkg.BaseApplication {
        public <init>();
    }
    -keep class com.my.pkg.SophixStubApplication$BaseApplication Stub

目的是防止真正Application的構(gòu)造方法被proguard混淆。

最后,需要把AndroidManifest里面的application改為這個新增的SophixStubApplication類:

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

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

6、總結(jié)一下,過程一共有四個步驟:

1、把此SophixStubApplication入口類添加進項目中,所有Sophix相關(guān)初始化放在此類中。并且不應(yīng)包含開發(fā)者的任何業(yè)務(wù)邏輯代碼。 若使用了MultiDex,也應(yīng)在SophixStubApplication的initSophix之前添加,并且需要記得在原來的Application里面去除MultiDex,避免重復(fù)調(diào)用導(dǎo)致問題。
2、 把RealApplicationStub的SophixEntry注解的內(nèi)容改為自己原先真正的MyRealApplication類。
3、混淆文件中確保某些內(nèi)容不被混淆。
4、AndroidManifest里面的application改為新增的SophixStubApplication入口類。

7、常見狀態(tài)碼:

一個補丁的加載一般分為三個階段: 查詢、預(yù)加載、加載,各個階段的常見狀態(tài)碼如下:

        //兼容老版本的code說明
        int CODE_LOAD_SUCCESS = 1;//加載階段, 成功
        int CODE_ERR_INBLACKLIST = 4;//加載階段, 失敗設(shè)備不支持
        int CODE_REQ_NOUPDATE = 6;//查詢階段, 沒有發(fā)布新補丁
        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;//預(yù)加載階段, 需要重啟
        int CODE_REQ_APPIDERR = 15;//查詢階段, appid異常
        int CODE_REQ_SIGNERR = 16;//查詢階段, 簽名異常
        int CODE_REQ_UNAVAIABLE = 17;//查詢階段, 系統(tǒng)無效
        int CODE_REQ_SYSTEMERR = 22;//查詢階段, 系統(tǒng)異常
        int CODE_REQ_CLEARPATCH = 18;//查詢階段, 一鍵清除補丁
        int CODE_REQ_TOOFAST = 19;//連續(xù)兩次請求不能小于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;//請求缺少必要參數(shù)異常
        //預(yù)加載階段的code說明
        int CODE_PRELOAD_SUCCESS = 100;//預(yù)加載成功
        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異常
        // 反射調(diào)用異常
        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、點擊設(shè)置,然后配置簽名信息,指定補丁包的輸出路徑(只第一次需要配置這些信息)


圖片.png

3、直接選擇舊包和新包(舊包指有bug的包,新包是修復(fù)完bug的包)
然后點擊 GO!
補丁包就會生成在你指定的輸出路徑下:


圖片.png
圖片.png

4、然后進入控制臺,添加版本,上傳補丁。
注意,版本號必須和項目保持一致。

// 默認(rèn)版本號是 1.0,會獲取當(dāng)前版本

   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

灰度發(fā)布:可指定修復(fù)補丁的手機數(shù)量
全量發(fā)布:用于生產(chǎn)環(huán)境。經(jīng)本地測試,灰度發(fā)布測試沒問題后,就可以全量發(fā)布了

特別注意:發(fā)布完畢,殺掉進程(而不是返回)才會生效。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容