*本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布
本文更新于2017年11月20日
前言
關(guān)于sophix集成和使用,網(wǎng)上有了很多前輩寫的博客。讀了很多,感覺都不太詳細和系統(tǒng)。所以自己嘗試寫sophix集成文章,本文包括四部分內(nèi)容:
- 控制臺開通移動熱修復(fù)
- 工程代碼快速接入
- 生成、上傳、調(diào)試補丁
- 補丁灰度發(fā)布、全量發(fā)布、機型過濾
關(guān)于sophix的原理和與其他熱修復(fù)框架的比較,戳官方文檔
阿里手淘團隊出書了,業(yè)界首部全方位系統(tǒng)介紹熱修復(fù)原理書籍,從阿里Sophix方案開發(fā)過程入手權(quán)威解讀!《深入探索Android熱修復(fù)技術(shù)原理》
這本書建議讀一讀。
話不多說,集成開始:
控制臺開通移動熱修復(fù)
阿里云控制臺的使用有點繞,要注意了,對照著一步一步來
- 登錄阿里云,開通移動熱修復(fù)
Ps:
如果自己進了阿里云官網(wǎng)首頁,怎么找熱修復(fù): 鼠標滑到 菜單欄 【產(chǎn)品】,彈出的菜單,找到白色字體類別【移動云】,移動云 的子菜單里找到【移動熱修復(fù)】

· 右上角登錄,可以使用淘寶賬號直接登錄。注冊一個也行。
· 左邊 點擊 立即開通。
沒開通的,會跳轉(zhuǎn)到一個頁面,告知 【確認開通】。
確認開通后,跳轉(zhuǎn)到控制臺的移動熱修復(fù)頁面,醬紫的

Ps:
如果讀者自己是通過點官網(wǎng)首頁左上角的【控制臺】,直接進入了【管理控制臺】,那怎么進到移動熱修復(fù)的控制臺頁面呢:看上面的截圖,菜單欄的 【產(chǎn)品與服務(wù)】,是以首字母排列的。找Y類-【移動熱修復(fù)】。點一下,就切換到移動熱修復(fù)的管理了。
截圖中 【創(chuàng)建App】是新開一個標簽頁,跳轉(zhuǎn)到 [移動云] 控制臺(Mobile Hub)去創(chuàng)建的,和當前處在的 [移動熱修復(fù)] 控制臺 不同,不要搞混。
- 點擊【創(chuàng)建App】,會提示先【創(chuàng)建產(chǎn)品】
產(chǎn)品下包含著 創(chuàng)建應(yīng)用(App),產(chǎn)品的名字隨便起。

- 點擊 藍色字體產(chǎn)品名稱 或 【管理】,進入 產(chǎn)品信息頁。

Ps:
在本頁的 應(yīng)用列表的App都有 查看信息 選項,這里用不到它,因為沒有我們需要的RSA密鑰。
點擊 【創(chuàng)建應(yīng)用】,填入App名(最好和項目名稱一致),應(yīng)用類型 選 Android,填入packageName。 (bundleId是iOS的標識)
創(chuàng)建成功后,在下方的應(yīng)用列表展示信息。
- 點擊 移動熱修復(fù),再點擊應(yīng)用列表 對應(yīng)App 的【管理】,查看 AppId、AppSecret、RSA密鑰
進入移動熱修復(fù)有兩種方法:
1.看上圖,可以在當前移動云 產(chǎn)品信息頁 ,點擊 移動熱修復(fù)標簽,
2.可以關(guān)掉當前網(wǎng)頁(還記得在移動熱修復(fù)控制臺【創(chuàng)建App】是新開一個標簽頁嗎)這樣也可以回到移動熱修復(fù)的頁面,再刷新一下。
第1種方法結(jié)果:

第2種方法結(jié)果:

點擊應(yīng)用列表【管理】,進入圖二

總之,一定要在創(chuàng)建完產(chǎn)品和應(yīng)用后,到 [移動熱修復(fù)] 標簽頁,才能查看到AppId,AppSecret,RSA密鑰。不要在移動云的產(chǎn)品處查看,那樣你是看不到RSA密鑰的。
關(guān)于 【管理控制臺】 的更多使用詳情, 戳這里
工程代碼快速接入
- 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文件
-
需要用到一下權(quán)限:
<! -- 網(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)限獲取。 -
application節(jié)點下添加如下配置:添加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這個方法進行設(shè)置。這個下面寫Java代碼時再說。
-
混淆配置
#基線包使用,生成mapping.txt
-printmapping mapping.txt
#生成的mapping.txt在app/buidl/outputs/mapping/release路徑下,移動到/app路徑下
#修復(fù)后的項目使用,保證混淆結(jié)果一致
#-applymapping mapping.txt
#hotfix
-keep class com.taobao.sophix.**{*;}
-keep class com.ta.utdid2.device.**{*;}
#防止inline
-dontoptimize
- Java代碼初始化接入
Sophix 3.1.6版本以后引入了新的初始化方式。
原來的初始化方式仍然可以使用,不過新方式將會帶來以下優(yōu)點:初始化與應(yīng)用原先業(yè)務(wù)代碼完全隔離,使得原先真正的Application可以修復(fù),并且減少了補丁預(yù)加載時間。而且,新方式已經(jīng)優(yōu)先支持Android 8.0版本。
本文使用這種新型方式。
1- 導(dǎo)入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)。
* AndroidManifest中設(shè)置application為此類,而SophixEntry中設(shè)為原先Application類。
* 注意原先Application里不需要再重復(fù)初始化Sophix,并且需要避免混淆原先Application類。
* 如有其它自定義改造,請咨詢官方后妥善處理。
*/
public class SophixStubApplication extends SophixApplication {
private final String TAG = "SophixStubApplication";
// 此處SophixEntry應(yīng)指定真正的Application,也就是你的應(yīng)用中原有的主Application,并且保證RealApplicationStub類名不被混淆。
@Keep
@SophixEntry(MyRealApplication.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) //三個參數(shù)分別對應(yīng)AndroidManifest里面的AppId、AppSecret、RSA密鑰,可以不在AndroidManifest設(shè)置而是用此函數(shù)來設(shè)置Secret。放到代碼里面進行設(shè)置可以自定義混淆代碼,更加安全,此函數(shù)的設(shè)置會覆蓋AndroidManifest里面的設(shè)置,如果對應(yīng)的值設(shè)為null,默認會在使用AndroidManifest里面的。
.setEnableDebug(true)//默認為false,設(shè)為true即調(diào)試模式下會輸出日志以及不進行補丁簽名校驗. 線下調(diào)試此參數(shù)可以設(shè)置為true, 它會強制不對補丁進行簽名校驗, 所有就算補丁未簽名或者簽名失敗也發(fā)現(xiàn)可以加載成功. 但是正式發(fā)布該參數(shù)必須為false, false會對補丁做簽名校驗, 否則就可能存在安全漏洞風(fēng)險。
.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.");
/** 不可以直接Process.killProcess(Process.myPid())來殺進程,這樣會擾亂Sophix的內(nèi)部狀態(tài)。
* 因此如果需要殺死進程,建議使用這個方法,它在內(nèi)部做一些適當處理后才殺死本進程。*/
instance.killProcessSafely();
}
}
}).initialize();
}
@Override
public void onCreate() {
super.onCreate();
// queryAndLoadNewPatch不可放在attachBaseContext 中,否則無網(wǎng)絡(luò)權(quán)限,建議放在后面任意時刻,如onCreate中
SophixManager.getInstance().queryAndLoadNewPatch();
/** 補丁在后臺發(fā)布之后, 并不會主動下行推送到客戶端, 客戶端通過調(diào)用queryAndLoadNewPatch方法查詢后臺補丁是否可用*/
}
}
初始化sophix務(wù)必放在attachBaseContext中,onCreate不需要自行實現(xiàn)。同時自定義的SophixStubApplication需要繼承com.taobao.sophix.SophixApplication。
這其中,關(guān)鍵一點是:
@Keep
@SophixEntry(MyRealApplication.class)
static class RealApplicationStub {}
SophixEntry應(yīng)指定項目中原先真正的Application(原項目里application的android::name指定的),這里用MyRealApplication指代。并且保證RealApplicationStub類名不被混淆。而SophixStubApplication的類名和包名可以自行取名。
這里的Keep是android.support包中的類,目的是為了防止這個內(nèi)部靜態(tài)類的類名被混淆,因為sophix內(nèi)部會反射獲取這個類的SophixEntry。如果項目中沒有依賴android.support的話,就需要在progurad里面手動指定RealApplicationStub不被混淆。
2- 然后,在proguard文件里面需要加上下面內(nèi)容:
-keepclassmembers class com.my.pkg.MyRealApplication {
public <init>();
}
# 如果不使用android.support.annotation.Keep則需加上此行
# -keep class com.my.pkg.SophixStubApplication$RealApplicationStub
目的是防止真正Application的構(gòu)造方法被proguard混淆。
最后,需要把AndroidManifest里面的application改為這個新增的SophixStubApplication類:
<application
android:name="com.my.pkg.SophixStubApplication"
... ...>
... ...
生成、上傳、調(diào)試補丁
下載打包工具:
patch補丁包生成需要使用到打補丁工具SophixPatchTool, 如還未下載打包工具,請前往下載Android打包工具。
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
該工具提供了Windows和macOS和Linux版本,Windows下運行SophixPatchTool.exe,macOS下運行SophixPatchTool.app,Linux下(Ubuntu 16.04 64bit最佳)運行SophixPatchTool。并且需要安裝Java環(huán)境且在JDK7或以上才能正常使用。
我是先 生成調(diào)試包,有問題的程序,Build apk,改名字為舊包.apk。然后修復(fù)完,再Build apk,改名字為新包.apk。這樣能看Log。測試成功后,再生成發(fā)布包,再測試一遍。

- 舊包:<必填> 有問題的APK。
- 新包:<必填> 修復(fù)過該問題APK。
- 日志:打開日志輸出窗口。
- 高級:展開高級選項。
- 設(shè)置:補丁輸出路徑和簽名文件設(shè)置。
- GO!:開始生成補丁。
點擊【高級】,彈出 補丁和簽名設(shè)置

- 強制冷啟動:勾選的話強制生成補丁包為需要冷啟動才能修復(fù)的格式。默認不選的話,工具會根據(jù)代碼變更情況自動選擇即時熱替換或者冷啟動修復(fù)。
- 不比較資源:打補丁時不比較資源的變化。
- 不比較SO庫:打補丁時不比較SO庫的變化。
所以,高級選項可以不做處理。
強制冷啟動:勾選的話強制生成補丁包為需要冷啟動才能修復(fù)的格式。默認不選的話,工具會根據(jù)代碼變更情況自動選擇即時熱替換或者冷啟動修復(fù)。
不比較資源:打補丁時不比較資源的變化。
不比較SO庫:打補丁時不比較SO庫的變化。
點擊【設(shè)置】

- 補丁輸出路徑:<必填> 指定生成補丁之后補丁的存放位置,必須是已存在的目錄。
- Key Store Path:<選填>本地的簽名文件的路徑,不輸入則不做簽名。
- Key Store Password:<選填>證書文件的密碼。
- Key Alias:<選填>Key的別名。
- Key Passwrod:<選填>Key的密碼。
下面的一般不做處理: - AES Key:<選填>自定義aes秘鑰, 必須是16位數(shù)字或字母的組合。必須與setAesKey中設(shè)置的秘鑰一致。
- Filter Class File:<選填>本地的白名單類列表文件的路徑,放進去的類不會再計算patch,文件格式: 一行一個類名。
Ps:
mac下的補丁工具若出現(xiàn)一打開就崩潰的情況,請將補丁工具移到“應(yīng)用程序”目錄下即可。
點擊 Go ,生成的補丁如下圖:

補丁文件名必須為:sophix-patch.jar。不能更改。
上傳補丁
-
首先進入 移動熱修復(fù) 管理控制臺
移動熱修復(fù) 管理控制臺 -
點擊App列表里的操作-【管理】,進入詳情頁
App詳情頁 點擊 【添加版本】,也就是應(yīng)用的版本號
這里的版本號一定要和工程里的gradle文件里記錄的一致。我截圖上的一個1.0和1.0.0。搞1.0.0測試了半天,沒結(jié)果,傻不傻。gradle里默認是“1.0”-
添加完版本,點擊應(yīng)用版本列表下的 【查看詳情】,進入版本詳情頁
版本詳情頁
點擊 【上傳補丁】,補丁版本列表更新。
-
點擊 補丁版本列表 的 【查看詳情】,進入 補丁詳情頁,可以查看補丁屬性和補丁狀態(tài)
補丁詳情頁
上圖有個二維碼,在正式發(fā)布前,我們用測試工具掃碼測試下。
測試工具是個apk。它是通過掃描補丁二維碼,下載到手機上,然后通過在apk界面上輸入你要測試的應(yīng)用包名,將補丁打到應(yīng)用里。
調(diào)試補丁
調(diào)試工具App地址:http://ams-hotfix-repo.oss-cn-shanghai.aliyuncs.com/hotfix_debug_tool-release.apk

那個截圖上的 【斷開連接應(yīng)用】,最開始是 【連接應(yīng)用】
- 先把你的有bug的apk安裝到手機上
- 然后打開該調(diào)試工具App,先輸入 bug應(yīng)用 包名,點 【連接應(yīng)用】
- 點 【掃描二維碼】,掃 網(wǎng)頁上 補丁詳情頁 的二維碼
接下來,就不用管了。它會下載補丁,并打到應(yīng)用上。
看到調(diào)試App界面是輸出信息,有以下幾條,就代表成功了。
app connect successful.
patch download success.
please restart app to reload new patch as exist old patch.
打開你的bug應(yīng)用,就可以看到變化了。
來個截圖示例,應(yīng)用源碼就是文章前面給的sample

SophixTest原來只顯示個 helloworld,經(jīng)過Sophix調(diào)試工具V3的打補丁后,再次打開SophixTest就變成了有福利字樣,并顯示張美女圖片。
補丁灰度發(fā)布、全量發(fā)布、機型過濾
注意事項:
- 支持多渠道包僅選用某個渠道包的補丁,只需要保證變化相同即可,不過對于不同的apk包最好進行全面的測試。
- 發(fā)布前請嚴格按照:掃碼內(nèi)測 => 灰度發(fā)布 => 全量發(fā)布的流程進行,以保證補丁包能夠正常在所有Android版本的機型上生效。

- 補丁狀態(tài):
- 等待中:補丁上傳成功,等待操作。
- 已灰度:補丁正在進行灰度發(fā)布。
- 已發(fā)布:補丁已全量發(fā)布至所有設(shè)備。
- 已停止:補丁發(fā)布行為已暫停。
灰度發(fā)布
在應(yīng)用版本詳情頁,點擊補丁版本列表里的【查看詳情】,進入 補丁詳情頁。

在剛剛上傳完補丁后,補丁處于 等待中 的狀態(tài),勾選 灰度發(fā)布。
設(shè)置完設(shè)備數(shù),客戶端拉取補丁會消耗該設(shè)備數(shù),達到灰度設(shè)備數(shù)后,灰度補丁自動置為停止狀態(tài)。
設(shè)備數(shù):指設(shè)備請求更新該補丁的次數(shù),并不等于絕對設(shè)備數(shù)。
例如:1個設(shè)備請求了2次更新該補丁,則會消耗掉2的設(shè)備數(shù)。
-
確認發(fā)布
點擊【確認發(fā)布】,補丁狀態(tài)為 已灰度 ,進入灰度發(fā)布狀態(tài)。
灰度發(fā)布狀態(tài)
這時,當用戶打開客戶端,就會拉取線上的補丁,修復(fù)程序。
還記得代碼中的queryAndLoadNewPatch()方法嗎,它的作用去看sample源碼注釋。
- 成功推送設(shè)備數(shù):每當有設(shè)備發(fā)起一次更新請求,且補丁下載成功,則記為一次成功推送。
- 累計加載設(shè)備數(shù):每當有設(shè)備成功加載該補丁,則記為一次累計加載。
注:
· 只會下載補丁版本號比當前應(yīng)用存在的補丁版本號高的補丁, 比如當前應(yīng)用已經(jīng)下載了補丁版本號為5的補丁, 那么只有后臺發(fā)布的補丁版本號>5才會重新下載.
· 在上傳新的補丁之后,要調(diào)試時,如果以往的補丁有處于 已灰度 或已發(fā)布狀態(tài),要停止發(fā)布。 如果不停止,最新的補丁處于等待中,也就是未發(fā)布。那么當你打開客戶端,它會拉取以往發(fā)布的補丁修復(fù)程序,這樣會影響你觀測調(diào)試結(jié)果。
· 后臺數(shù)據(jù)可能有少許延遲。
- 停止發(fā)布
點擊【停止發(fā)布】后,用戶選擇停止發(fā)布后,系統(tǒng)將停止該補丁的繼續(xù)發(fā)布,但已加載該補丁的設(shè)備會依然保持安裝該補丁的狀態(tài)。
界面變成:

- 繼續(xù)發(fā)布
用戶點擊【繼續(xù)發(fā)布】后,將可以重新設(shè)置發(fā)布規(guī)則。
如果當前版本在停止前處于灰度中,繼續(xù)發(fā)布可以:
· 重設(shè)灰度發(fā)布規(guī)則,新的規(guī)則中設(shè)備數(shù)必須大于之前的值。
· 改為全量發(fā)布。

所以,從灰度發(fā)布到全量發(fā)布的步驟是
· 先在補丁詳情頁勾選灰度發(fā)布,點擊確認發(fā)布
· 推送完所有灰度設(shè)備后,點擊停止發(fā)布
· 再點擊繼續(xù)發(fā)布,彈出框里選擇全量發(fā)布
如果當前版本在停止前處于全量發(fā)布,繼續(xù)發(fā)布可以:
繼續(xù)全量發(fā)布。 --- 對,你沒看錯,就是逗你玩!
- 選擇回滾
用戶選擇回滾的目標補丁后,所有該應(yīng)用版本下的設(shè)備都會回滾到目標補丁的版本。
使用回滾功能必需要具備一下幾個條件:
· 當前的版本已停止發(fā)布。
· 該版本之前存在至少一個全量發(fā)布的歷史版本。
全量發(fā)布
選擇全量發(fā)布后,將對所有安裝了當前應(yīng)用版本(即之前創(chuàng)建應(yīng)用時所填寫的應(yīng)用版本號)的設(shè)備推送該補丁。
與灰度發(fā)布類似,在全量發(fā)布會可以根據(jù)自身需要停止本次全量發(fā)布,停止發(fā)布后可以選擇:
· 繼續(xù)全量發(fā)布。
· 回滾版本(如果存在歷史版本)
添加過濾機型
全量發(fā)布后,我們可以添加過濾機型。
不全量發(fā)布是不可以添加機型過濾的

在App版本詳情頁,點擊【添加過濾機型】

這里對過濾機型的彈出框參數(shù)進行說明:
- 系統(tǒng)版本
系統(tǒng)版本是指手機所使用的OS的版本。
在控制臺中,有相應(yīng)的系統(tǒng)版本列表可供選擇。如果列表中沒有需要自定義,請按如下標準獲取系統(tǒng)版本。
android.os.Build.VERSION.RELEASE
例如系統(tǒng)版本結(jié)果是:7.1
- 手機品牌
手機品牌是指手機貼牌商標代表的品牌,需要區(qū)別手機制造商,手機制造商可能會生產(chǎn)多個品牌,一個品牌也可能是多個制造商生產(chǎn)。
在控制臺中,我們有相應(yīng)的品牌列表供選擇使用。如果需要自定義,請按如下標準獲取手機品牌,注意實際過濾時不區(qū)分大小寫。
android.os.Build.BRAND
例如手機品牌是:Xiaomi
- 手機機型
手機機型是指某個手機品牌下手機具體的型號。
目前由于手機機型龐雜,沒有提供選擇列表供選擇,后續(xù)會支持。填寫手機機型時請按如下標準,不區(qū)分大小寫。
android.os.Build.MODEL
例如手機型號是:OPPO R11
【注意】如果想設(shè)置全部機型,請在自定義機型里面,輸入 :all
(就是 冒號+all)
到這里,sophix集成的全部內(nèi)容就結(jié)束了。阿里熱修復(fù)官方的文檔有點瑣碎,我把重點和注意點都挑出來了。讀完這四篇,相信你會迅速集成sophix到自己的應(yīng)用里。
這再給出官方接入文檔地址,給還想看官方文檔的朋友。官方接入文檔




