相信很多人都認(rèn)識(shí)了解過(guò) 熱修復(fù)、熱更新、熱補(bǔ)丁(對(duì)于這個(gè)技術(shù)也沒(méi)有特別標(biāo)準(zhǔn)的一種叫法,下面我統(tǒng)一叫熱更新),之后的一年里,各種熱更新方案如雨后春筍般出現(xiàn),比較耳熟能詳?shù)木陀蠳uwa、Tinker、Andfix 和 Dexposed 等等,他們之間的區(qū)別以及優(yōu)缺點(diǎn)就不在這里討論了,鑒于它們的實(shí)際使用和局限性,美團(tuán)的開發(fā)團(tuán)隊(duì)就腦洞大開了。

去年 Google 高調(diào)發(fā)布了 Android Studio 2.0,其中最重要的新特性Instant Run,實(shí)現(xiàn)了對(duì)代碼修改的實(shí)時(shí)生效(熱插拔)。美團(tuán)開發(fā)團(tuán)隊(duì)在了解 Instant Run 原理之后,實(shí)現(xiàn)了一個(gè)兼容性更強(qiáng)的熱更新方案,這就是產(chǎn)品化的hotpatch框架—–Robust,對(duì)于 Robust 的原理我們后面的文章再討論,本篇只針對(duì)想快速上手的入門講解
Android熱更新方案Robust開源,新增自動(dòng)化補(bǔ)丁工具
美團(tuán) Robust 的 github demo 地址

Robust 為每個(gè)類新增了一個(gè)類型為 ChangeQuickRedirect 的靜態(tài)變量,并且在每個(gè)方法前,增加判斷該變量是否為空的邏輯,如果不為空,走打基礎(chǔ)包時(shí)插樁的邏輯,否則走正常邏輯。
使用步驟
1.集成了 Robust 后,生成 apk。保存期間的混淆文件 mapping.txt,以及 Robust 生成記錄文件 methodMap.robust
2.使用注解 @Modify 或者方法 RobustModify.modify() 標(biāo)注需要修復(fù)的方法
3.開啟補(bǔ)丁插件,執(zhí)行生成 apk 命令,獲得補(bǔ)丁包 patch.jar
4.通過(guò)推送或者接口的形式,通知 app 有補(bǔ)丁,需要修復(fù)
5.加載補(bǔ)丁文件不需要重新啟動(dòng)應(yīng)用
集成方法
1、在項(xiàng)目最外層的 build.gradle 添加兩處插件
classpath 'com.meituan.robust:gradle-plugin:0.3.3'
classpath 'com.meituan.robust:auto-patch-plugin:0.3.3'
2、然后在項(xiàng)目的 build.gradle 添加
//apply plugin: 'auto-patch-plugin'(生成補(bǔ)丁的時(shí)候打開)
apply plugin: 'robust'(生成apk的時(shí)候打開)
compile 'com.meituan.robust:robust:0.3.3'
3、需要手動(dòng) copy 一份 robust.xml 的配置文件到app的目錄下,該文件各個(gè)配置注釋的很清楚,若沒(méi)特殊要求,不需要修改
<?xml version="1.0" encoding="utf-8"?>
<resources>
<switch>
<!--true代表打開Robust,請(qǐng)注意即使這個(gè)值為true,Robust也默認(rèn)只在Release模式下開啟-->
<!--false代表關(guān)閉Robust,無(wú)論是Debug還是Release模式都不會(huì)運(yùn)行robust-->
<turnOnRobust>true</turnOnRobust>
<!--<turnOnRobust>false</turnOnRobust>-->
<!--是否開啟手動(dòng)模式,手動(dòng)模式會(huì)去尋找配置項(xiàng)patchPackname包名下的所有類,自動(dòng)的處理混淆,然后把patchPackname包名下的所有類制作成補(bǔ)丁-->
<!--這個(gè)開關(guān)只是把配置項(xiàng)patchPackname包名下的所有類制作成補(bǔ)丁,適用于特殊情況,一般不會(huì)遇到-->
<!--<manual>true</manual>-->
<manual>false</manual>
<!--是否強(qiáng)制插入插入代碼,Robust默認(rèn)在debug模式下是關(guān)閉的,開啟這個(gè)選項(xiàng)為true會(huì)在debug下插入代碼-->
<!--但是當(dāng)配置項(xiàng)turnOnRobust是false時(shí),這個(gè)配置項(xiàng)不會(huì)生效-->
<!--<forceInsert>true</forceInsert>-->
<forceInsert>false</forceInsert>
<!--是否捕獲補(bǔ)丁中所有異常,建議上線的時(shí)候這個(gè)開關(guān)的值為true,測(cè)試的時(shí)候?yàn)閒alse-->
<catchReflectException>true</catchReflectException>
<!--<catchReflectException>false</catchReflectException>-->
<!--是否在補(bǔ)丁加上log,建議上線的時(shí)候這個(gè)開關(guān)的值為false,測(cè)試的時(shí)候?yàn)閠rue-->
<!--<patchLog>true</patchLog>-->
<patchLog>false</patchLog>
<!--項(xiàng)目是否支持progaurd-->
<proguard>true</proguard>
<!--<proguard>false</proguard>-->
<!--項(xiàng)目是否支持ASM進(jìn)行插樁,默認(rèn)使用ASM,推薦使用ASM,Javaassist在容易和其他字節(jié)碼工具相互干擾-->
<useAsm>true</useAsm>
<!--<useAsm>false</useAsm>-->
</switch>
<!--需要熱補(bǔ)的包名或者類名,這些包名下的所有類都被會(huì)插入代碼-->
<!--這個(gè)配置項(xiàng)是各個(gè)APP需要自行配置,就是你們App里面你們自己代碼的包名,
這些包名下的類會(huì)被Robust插入代碼,沒(méi)有被Robust插入代碼的類Robust是無(wú)法修復(fù)的-->
<packname name="hotfixPackage">
<name>com.project</name>
</packname>
<!--不需要Robust插入代碼的包名,Robust庫(kù)不需要插入代碼,如下的配置項(xiàng)請(qǐng)保留,還可以根據(jù)各個(gè)APP的情況執(zhí)行添加-->
<exceptPackname name="exceptPackage">
</exceptPackname>
<!--補(bǔ)丁的包名,請(qǐng)保持和類PatchManipulateImp中fetchPatchList方法中設(shè)置的補(bǔ)丁類名保持一致( setPatchesInfoImplClassFullName("com.meituan.robust.patch.PatchesInfoImpl")),
各個(gè)App可以獨(dú)立定制,需要確保的是setPatchesInfoImplClassFullName設(shè)置的包名是如下的配置項(xiàng),類名必須是:PatchesInfoImpl-->
<patchPackname name="patchPackname">
<name>com.project</name>
</patchPackname>
<!--自動(dòng)化補(bǔ)丁中,不需要反射處理的類,這個(gè)配置項(xiàng)慎重選擇-->
<noNeedReflectClass name="classes no need to reflect">
</noNeedReflectClass>
</resources>
現(xiàn)在可以編譯項(xiàng)目 時(shí)間會(huì)比較長(zhǎng)

先解釋一下,在生成 apk 的時(shí)候使用 apply plugin:'robust',該插件會(huì)生成打補(bǔ)丁時(shí)需要的方法記錄文件 methodMap.robust,該文件在打補(bǔ)丁的時(shí)候用來(lái)區(qū)別到底哪些方法需要被修復(fù),所以有它才能打補(bǔ)丁。而上文所說(shuō)的還有 mapping.txt 文件,該文件列出了原始的類,方法和字段名與混淆后代碼間的映射。這個(gè)文件很重要,可以用它來(lái)翻譯被混淆的代碼。但也不是必須的,如果不需要混淆,可以不保留。這兩個(gè)文件在生成apk后,分別在 build/outputs/robust/methodsMap.robust,build/outputs/mapping/mapping.txt(需要開啟混淆后才會(huì)出現(xiàn)),我們需要自己分別拷貝到 app/robust 下,在 app 目錄新建個(gè)叫 robust 的文件夾,把這兩個(gè)文件放進(jìn)去就 ok 了。

完成了第二步了。我們得到了 apk ,mapping.txt,methodMap.robust ,有了它們我們?cè)倮^續(xù)生成補(bǔ)丁 patch.jar。
加載補(bǔ)丁的方法
new PatchExecutor(getActivity(), new PatchManipulateImp(), new RobustCallBack() {
@Override
public void onPatchListFetched(boolean b, boolean b1) {
}
@Override
public void onPatchFetched(boolean b, boolean b1, Patch patch) {
}
@Override
public void onPatchApplied(boolean b, Patch patch) {
}
@Override
public void logNotify(String s, String s1) {
}
@Override
public void exceptionNotify(Throwable throwable, String s) {
}
}).start();
注意 PatchManipulateImp
關(guān)于PatchManipulateImp這個(gè)類控制了補(bǔ)丁的加載策略,代碼中也增加不少的注釋,在這個(gè)類中實(shí)際負(fù)責(zé)補(bǔ)丁的下載、校驗(yàn)和使用策略等,這個(gè)類需要實(shí)現(xiàn)如下幾個(gè)方法
要加載補(bǔ)丁肯定得知道 patch.jar 放在哪啊是吧,打開看一眼(不要害怕只有很少代碼),為了方便展示,就把不太重要的三個(gè)方法縮起來(lái)了,copy 方法是普通文件拷貝的IO流,verifyPatch 方法本來(lái)是驗(yàn)證補(bǔ)丁有效性的,后來(lái)發(fā)現(xiàn)對(duì)普通使用者沒(méi)有那么高要求,就改成了備份補(bǔ)丁的回調(diào)了,ensurePatchExist 方法就檢測(cè)補(bǔ)丁存在與否,好了大概知道其它的方法就詳細(xì)介紹下主要方法 fetchPatchList,回顧一下上面的xxx再聯(lián)想到這里吧,既然我們要加載補(bǔ)丁,那么我們得知道補(bǔ)丁在哪啊,這個(gè)方法就是把補(bǔ)丁找出來(lái)給上面那個(gè)誰(shuí)用的。所以說(shuō),補(bǔ)丁的位置你可以根據(jù)拉取下來(lái)保存的位置來(lái)找出來(lái),把路徑給 setLocalPath 就好。再有就 setPatchesInfoImplClassFullName 的包名需要和 robust.xml 配置的一樣


新增加一個(gè)頁(yè)面來(lái)測(cè)試補(bǔ)丁
public class RobustActivity extends AppCompatActivity {
private TextView text2;
@Override
@Modify
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_robust);
text2 = findViewById(R.id.text2);
// text2.setText(getString());
text2.setText(getinfo());
}
private String getString() {
return "hello word";
}
@Add
public String getinfo() {
StringBuilder msg = new StringBuilder();
for (int i = 0; i < 10; i++) {
msg.append(i + "\n");
}
return msg.toString();
}
}
制作補(bǔ)丁并使用
我們所需要做的跳轉(zhuǎn)后效果是修改一下textview顯示內(nèi)容而已,那么在被修改的頁(yè)面需要怎么標(biāo)注被修改的方法呢,就像這樣,先科普一下,在 robust 的注意事項(xiàng)里面已經(jīng)提到過(guò),修改方法和字段會(huì)有一些局限性,那是因?yàn)?android 本身ProGuard的內(nèi)聯(lián)、優(yōu)化導(dǎo)致的。所以要繞過(guò)這個(gè)本身的問(wèn)題,要必須遵循一些規(guī)律了...以后再介紹這個(gè)。修改完這些,我們?cè)偃?build.gradle 修改一下,開啟打補(bǔ)丁那個(gè),關(guān)閉生成apk那個(gè)。之后再在終端執(zhí)行一遍生成apk的命令行。直到終端那里出現(xiàn) auto patch end successfully
美團(tuán)已經(jīng)提供了自動(dòng)化生成補(bǔ)丁的工具
在項(xiàng)目的 build.gradle apply plugin: 'auto-patch-plugin'打開
注釋掉生成apk的插件
再次點(diǎn)擊 assembleRelease
出現(xiàn) auto patch end successfully 表示補(bǔ)丁已經(jīng)生成 此時(shí)在robust文件里面可以看到patch.jar

點(diǎn)擊load_path

如果看到 apply result true 那么就大功告成了。
再點(diǎn)擊jump
便可以看到加載的是補(bǔ)丁里面的方法
最后來(lái)張效果圖

