一、前言:
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)先熱啟動。

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

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

3、進入控制臺

三、集成阿里云熱修復(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、點擊軟件首次打開

2、點擊設(shè)置,然后配置簽名信息,指定補丁包的輸出路徑(只第一次需要配置這些信息)

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


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"
}
補丁后臺的版本也要一致:

依次進行下列步驟即可:



灰度發(fā)布:可指定修復(fù)補丁的手機數(shù)量
全量發(fā)布:用于生產(chǎn)環(huán)境。經(jīng)本地測試,灰度發(fā)布測試沒問題后,就可以全量發(fā)布了
特別注意:發(fā)布完畢,殺掉進程(而不是返回)才會生效。