準(zhǔn)備
-
在APP的
build.gradle
文件中,添加依賴:apply plugin: 'com.android.application' //制作補(bǔ)丁時將這個打開,auto-patch-plugin緊跟著com.android.application //apply plugin: 'auto-patch-plugin' apply plugin: 'robust' compile 'com.meituan.robust:robust:0.4.99'
-
在整個項目的
build.gradle
中加入buildscript { repositories { jcenter() } dependencies { classpath 'com.meituan.robust:gradle-plugin:0.4.99' classpath 'com.meituan.robust:auto-patch-plugin:0.4.99' } }
將
robust.xml
文件拷到src
同級目錄:
-
實(shí)現(xiàn)
PatchManipulate.java
和RobustCallBack.java
兩個接口-
PatchManipulate.java
說明:/** * Created by hedex on 16/6/20. */ public abstract class PatchManipulate { /** * 獲取補(bǔ)丁列表 * * @param context * @return 相應(yīng)的補(bǔ)丁列表 */ protected abstract List<Patch> fetchPatchList(Context context); /** * 驗(yàn)證補(bǔ)丁文件md5是否一致 * 如果不存在,則動態(tài)下載 * * @param context * @param patch * @return 校驗(yàn)結(jié)果 */ protected abstract boolean verifyPatch(Context context, Patch patch); /** * 努力確保補(bǔ)丁文件存在,驗(yàn)證md5是否一致。 * 如果不存在,則動態(tài)下載 * * @param patch * @return 是否存在 */ protected abstract boolean ensurePatchExist(Patch patch); }
特別說明:
patch.setLocalPath()
的字符串參數(shù)碍脏,必須跟robust.xml
配置的patchPackname
字段中的字符串一致,并且類名一定要是PatchesInfoImpl
-
RobustCallBack.java
方法說明:/** * Created by hedex on 17/1/22. */ public interface RobustCallBack { /** * 獲取補(bǔ)丁列表后稍算,回調(diào)此方法 * * @param result 補(bǔ)丁 * @param isNet 補(bǔ)丁 */ void onPatchListFetched(boolean result, boolean isNet, List<Patch> patches); /** * 在獲取補(bǔ)丁后典尾,回調(diào)此方法 * * @param result 結(jié)果 * @param patch 補(bǔ)丁 */ void onPatchFetched(boolean result, boolean isNet, Patch patch); /** * 在補(bǔ)丁應(yīng)用后,回調(diào)此方法 * * @param result 結(jié)果 * @param patch 補(bǔ)丁 */ void onPatchApplied(boolean result, Patch patch); void logNotify(String log, String where); void exceptionNotify(Throwable throwable, String where); }
-
使用
-
基礎(chǔ)包的預(yù)插樁
基礎(chǔ)包的預(yù)插樁配置主要在
robust.xml
文件中配置糊探,主要包括:標(biāo)簽 說明 turnOnRobust 是否打開Robust(只在Release模式下有效) forceInsert 是否強(qiáng)制插入代碼钾埂。如果為true,則在debug模式下,也會插入代碼 catchReflectException 是否捕獲補(bǔ)丁中所有異常科平, (建議上線的時候這個開關(guān)的值為true) patchLog 是否在補(bǔ)丁加上log褥紫, (建議上線的時候這個開關(guān)的值為false) proguard 項目是否支持proguard,(如果與項目配置不一致瞪慧,打包時會出錯) useAsm 插樁是否使用ASM髓考,否則使用javaassist (默認(rèn)使用ASM,推薦使用ASM弃酌,Javaassist在容易和其他字節(jié)碼工具相互干擾) forceInsertLambda 針對Java8級別的Lambda表達(dá)式氨菇,編譯為private級別的javac函數(shù),此時由開發(fā)者決定是否進(jìn)行插樁處理 <packname name="hotfixPackage"> 需要插樁的包名妓湘,或者類名 <exceptPackname name="exceptPackage"> 不需要插樁的包名查蓉,或者類名 <patchPackname name="patchPackname"> 補(bǔ)丁的包名,(這里設(shè)置的包名榜贴,必須和PatchManipulateImp中fetchPatchList方法中設(shè)置的補(bǔ)丁類名保持一致) 準(zhǔn)備就緒后豌研,在安裝或者輸出APK時,Robust就會在
build/outputs/robust/
目錄下輸出methodsMap.robust
和mapping.txt
。這兩個文件非常重要鹃共,在打包輸出后一定要和APK一起保存鬼佣。 -
打補(bǔ)丁包
在修改代碼之前,需要確定需要打補(bǔ)丁的APK是哪個版本及汉,并將對應(yīng)的
methodsMap.robust
和mapping.txt
文件拷貝到app/robust
文件下沮趣。-
將APP的
build.gradle
中的加入//制作補(bǔ)丁時將這個打開屯烦,auto-patch-plugin緊跟著com.android.application apply plugin: 'auto-patch-plugin'
-
然后開始修改需要修改的代碼:
- 支持方法坷随,類的新增 ------------
@Add
或者RobustModify.modify()
- 方法的修改 ------------------------
@Modify
- 支持新增字段暫時在內(nèi)測,可以通過新增類來實(shí)現(xiàn)
- 支持方法坷随,類的新增 ------------
-
運(yùn)行程序(安裝驻龟,編譯或者打APK操作都可以)
這次操作會失敗温眉,目的只是為了生成
patch.jar
文件,會拋出java.lang.RuntimeException: auto patch end successfully
-
下發(fā)補(bǔ)丁 && 補(bǔ)丁包加載
生成補(bǔ)丁包之后翁狐,然后就是如何將
patch.jar
下發(fā)到線上應(yīng)用上类溢,或者說,線上應(yīng)用如何得到補(bǔ)丁包露懒。在Demo演示階段闯冷,是將patch.jar
直接push到手機(jī)上,但是線上我們就需要讓應(yīng)用自己將補(bǔ)丁包下載到本地懈词,并自動加載補(bǔ)丁包蛇耀。- 在應(yīng)用冷啟動時,需要檢查是否有新的補(bǔ)丁包坎弯,如果有纺涤,則下載,之后抠忘,驗(yàn)證本地補(bǔ)丁包是否有效撩炊,并加載所有補(bǔ)丁包
- 在應(yīng)用處于運(yùn)行階段,如果有補(bǔ)丁包更新崎脉,需要Push推送拧咳,提醒應(yīng)用主動拉新補(bǔ)丁包,并加載新的補(bǔ)丁包
總結(jié)
優(yōu)點(diǎn)
- 由于使用的ASM技術(shù)插樁囚灼,Classloader進(jìn)行的類加載骆膝,所以高兼容,高穩(wěn)定性
- 補(bǔ)丁可以實(shí)時加載啦撮,實(shí)時生效
- 支持ProGuard的混淆谭网,內(nèi)聯(lián),優(yōu)化等操作
缺點(diǎn)
- 代碼是侵入式的赃春,會在原有的類中加入相關(guān)代碼
- 會增大apk的體積愉择,平均一個函數(shù)會比原來增加17.47個字節(jié),10萬個函數(shù)會增加1.67M
坑
目前0.4.99對最新的Gradle 6.5是不支持的(issue)
-
Gradle 3.6及以上的版本默認(rèn)啟用了R8,會將插入的changeQuickRedirect變量優(yōu)化掉锥涕,需要在混淆文件proguard-rules.pro中加入以下代碼:
-keepclassmembers class **{ public static com.meituan.robust.ChangeQuickRedirect *;}
-
對于方法的返回值是this的情況現(xiàn)在支持不好衷戈,比如builder模式, 但在制作補(bǔ)丁代碼時层坠,可以通過如下方式來解決殖妇,增加一個類來包裝一下(如下面的B類)
method a(){ return this; }
改為
method a(){ return new B().setThis(this).getThis(); }
不支持構(gòu)造方法的修復(fù)
不支持資源和SO的修復(fù)
使用時需要注意的點(diǎn) (每一步的操作都不復(fù)雜,但是容易漏掉步驟)
- 打基礎(chǔ)包時破花,如果需要在Debug插樁谦趣,需要將打開
robust.xml
中的forceInsert
設(shè)置成true - 打完基礎(chǔ)包之后的
methodsMap.robust
和mapping.txt
需要與APK包一起保存,方便查找 - 打補(bǔ)丁包時:將對應(yīng)的
methodsMap.robust
和mapping.txt
拷貝到app/robust
目錄座每,打開apply plugin: 'auto-patch-plugin'
自動化打包插件
小結(jié):
- 如果項目對熱修復(fù)要求不高前鹅,只是需要更新一些Java層代碼,并且考慮到穩(wěn)定性峭梳,兼容性舰绘,Robust都是一個不錯的熱修復(fù)方案。
- 目前Robust方案葱椭,美團(tuán)只是開源的代碼熱修復(fù)方案:前期的基礎(chǔ)包插樁捂寿,以及補(bǔ)丁的自動化,對于補(bǔ)丁的加載策略并沒有具體的方案孵运,并沒有像Sophix一樣提供服務(wù)秦陋,只是預(yù)留了接口。具體的方案需要根據(jù)不同的需求自己實(shí)現(xiàn)
-
是讓CI持續(xù)集成自動打補(bǔ)丁包(應(yīng)該是不能交給CI去做) - 本地修改后掐松,直接打補(bǔ)丁包踱侣,手動上傳到服務(wù)器 (涉及到一個服務(wù)器的問題,需要一個服務(wù)器去管理我們的補(bǔ)丁包)
-
- 是否需要支持熱更新大磺,如果需要抡句,服務(wù)器還需要提供push推送服務(wù),當(dāng)有新的補(bǔ)丁包時杠愧,需要推送給APP待榔,讓APP主動下載補(bǔ)丁,并加載流济。