android 世面上的熱更新方案有很多,例如QQ超級補丁 QZone 微信補丁方案 TinKer 阿里的AndFix 和美團的Robust 具體他們的優缺點網上一大堆 感興趣的可以去深入瞭解一下 在這裏就不一一說了,這裏主要說的是美團的Robust的集成步驟
1.在項目最外層的 build.gradle 添加兩處插件
classpath 'com.meituan.robust:gradle-plugin:0.4.91'
classpath 'com.meituan.robust:auto-patch-plugin:0.4.91
2.在項目App下面的 build.gradle 添加如下依賴
//製作補丁時將這個打開,auto-patch-plugin緊跟着com.android.application
// apply plugin: 'auto-patch-plugin'
apply plugin: 'robust'
dependencies {
implementation 'com.meituan.robust:robust:0.4.91'
}
3.在App文件夾下創建 robust.xml 文件 內容可以copy 下面的內容 具體的設置都有很詳細的註釋
<?xml version="1.0" encoding="utf-8"?>
<resources>
<switch>
<!--true代表打開Robust,請注意即使這個值爲true,Robust也默認只在Release模式下開啓-->
<!--false代表關閉Robust,無論是Debug還是Release模式都不會運行robust-->
<turnOnRobust>true</turnOnRobust>
<!--<turnOnRobust>false</turnOnRobust>-->
<!--是否開啓手動模式,手動模式會去尋找配置項patchPackname包名下的所有類,自動的處理混淆,然後把patchPackname包名下的所有類製作成補丁-->
<!--這個開關只是把配置項patchPackname包名下的所有類製作成補丁,適用於特殊情況,一般不會遇到-->
<!--<manual>true</manual>-->
<manual>false</manual>
<!--是否強制插入插入代碼,Robust默認在debug模式下是關閉的,開啓這個選項爲true會在debug下插入代碼-->
<!--但是當配置項turnOnRobust是false時,這個配置項不會生效-->
<!--<forceInsert>true</forceInsert>-->
<forceInsert>false</forceInsert>
<!--是否捕獲補丁中所有異常,建議上線的時候這個開關的值爲true,測試的時候爲false-->
<catchReflectException>true</catchReflectException>
<!--<catchReflectException>false</catchReflectException>-->
<!--是否在補丁加上log,建議上線的時候這個開關的值爲false,測試的時候爲true-->
<patchLog>true</patchLog>
<!-- <patchLog>false</patchLog>-->
<!--項目是否支持progaurd-->
<!-- <proguard>true</proguard>-->
<proguard>false</proguard>
<!--項目是否支持ASM進行插樁,默認使用ASM,推薦使用ASM,Javaassist在容易和其他字節碼工具相互干擾-->
<useAsm>true</useAsm>
<!--<useAsm>false</useAsm>-->
</switch>
<!--需要熱補的包名或者類名,這些包名下的所有類都被會插入代碼-->
<!--這個配置項是各個APP需要自行配置,就是你們App裏面你們自己代碼的包名,
這些包名下的類會被Robust插入代碼,沒有被Robust插入代碼的類Robust是無法修復的-->
<packname name="hotfixPackage">
<name>com.meituan</name>
<name>com.sankuai</name>
<name>com.dianping</name>
<name>com.lysoft.robustdemo</name>
</packname>
<!--不需要插Robust入代碼的包名,Robust庫不需要插入代碼,如下的配置項請保留,還可以根據各個APP的情況執行添加-->
<exceptPackname name="exceptPackage">
<name>com.meituan.robust</name>
<name>com.meituan.sample.extension</name>
</exceptPackname>
<!--補丁的包名,請保持和類PatchManipulateImp中fetchPatchList方法中設置的補丁類名保持一致( setPatchesInfoImplClassFullName("com.meituan.robust.patch.PatchesInfoImpl")),
各個App可以獨立定製,需要確保的是setPatchesInfoImplClassFullName設置的包名是如下的配置項,類名必須是:PatchesInfoImpl-->
<patchPackname name="patchPackname">
<!-- <name>com.meituan.robust.patch</name>-->
<name>com.lysoft.robustdemo</name>
</patchPackname>
<!--自動化補丁中,不需要反射處理的類,這個配置項慎重選擇-->
<noNeedReflectClass name="classes no need to reflect">
</noNeedReflectClass>
</resources>
接下來你要先把如何加載補丁的邏輯 寫出來 我這裏是點擊按鈕去加載手機SD卡里面我提前放好的補丁jar包
new PatchExecutor(getApplicationContext(), new PatchManipulateImp(), new RobustCallBackSample()).start();
public class PatchManipulateImp extends PatchManipulate {
/***
* connect to the network ,get the latest patches
* l聯網獲取最新的補丁
* @param context
*
* @return
*/
@Override
protected List<Patch> fetchPatchList(Context context) {
//將app自己的robustApkHash上報給服務端,服務端根據robustApkHash來區分每一次apk build來給app下發補丁
//apkhash is the unique identifier for apk,so you cannnot patch wrong apk.
String robustApkHash = RobustApkHashUtils.readRobustApkHash(context);
Log.w("robust","robustApkHash :" + robustApkHash);
//connect to network to get patch list on servers
//在這裏去聯網獲取補丁列表
Patch patch = new Patch();
patch.setName("Robust補丁1");
//we recommend LocalPath store the origin patch.jar which may be encrypted,while TempPath is the true runnable jar
//LocalPath是存儲原始的補丁文件,這個文件應該是加密過的,TempPath是加密之後的,TempPath下的補丁加載完畢就刪除,保證安全性
//這裏面需要設置一些補丁的信息,主要是聯網的獲取的補丁信息。重要的如MD5,進行原始補丁文件的簡單校驗,以及補丁存儲的位置,這邊推薦把補丁的儲存位置放置到應用的私有目錄下,保證安全性
// String localPath=Environment.getExternalStorageDirectory().getPath()+ File.separator+"0Rotbust"+File.separator + "patch";
String localPath=Environment.getExternalStorageDirectory().getPath()+ File.separator+"0Rotbust"+ File.separator + "patch";
patch.setLocalPath(localPath);
// patch.setTempPath(localPath);
Log.w("robust","localPath :" + localPath);
//setPatchesInfoImplClassFullName 設置項各個App可以獨立定製,需要確保的是setPatchesInfoImplClassFullName設置的包名是和xml配置項patchPackname保持一致,而且類名必須是:PatchesInfoImpl
//請注意這裏的設置
patch.setPatchesInfoImplClassFullName("com.lysoft.robustdemo.PatchesInfoImpl");
List<Patch> patches = new ArrayList<Patch>();
patches.add(patch);
return patches;
}
/**
*
* @param context
* @param patch
* @return
*
* you can verify your patches here
*/
@Override
protected boolean verifyPatch(Context context, Patch patch) {
//do your verification, put the real patch to patch
//放到app的私有目錄
patch.setTempPath(context.getCacheDir()+ File.separator+"robust"+File.separator + "patch");
//in the sample we just copy the file
try {
copy(patch.getLocalPath(), patch.getTempPath());
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("將源修補程序複製到本地修補程序時出錯,路徑中沒有執行修補程序 "+patch.getTempPath());
}
return true;
}
public void copy(String srcPath,String dstPath) throws IOException {
File src=new File(srcPath);
if(!src.exists()){
throw new RuntimeException("source patch does not exist ");
}
File dst=new File(dstPath);
if(!dst.getParentFile().exists()){
dst.getParentFile().mkdirs();
}
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
} finally {
out.close();
}
} finally {
in.close();
}
}
/**
*
* @param patch
* @return
*
* you may download your patches here, you can check whether patch is in the phone
*/
@Override
protected boolean ensurePatchExist(Patch patch) {
return true;
}
}
public class RobustCallBackSample implements RobustCallBack {
@Override
public void onPatchListFetched(boolean result, boolean isNet, List<Patch> patches) {
Log.d("RobustCallBack", "onPatchListFetched result: " + result);
Log.d("RobustCallBack", "onPatchListFetched isNet: " + isNet);
for (Patch patch : patches) {
Log.d("RobustCallBack", "onPatchListFetched patch: " + patch.getName());
}
}
@Override
public void onPatchFetched(boolean result, boolean isNet, Patch patch) {
Log.d("RobustCallBack", "onPatchFetched result: " + result);
Log.d("RobustCallBack", "onPatchFetched isNet: " + isNet);
Log.d("RobustCallBack", "onPatchFetched patch: " + patch.getName());
}
@Override
public void onPatchApplied(boolean result, Patch patch) {
Log.d("RobustCallBack", "onPatchApplied result: " + result);
Log.d("RobustCallBack", "onPatchApplied patch: " + patch.getName());
}
@Override
public void logNotify(String log, String where) {
Log.d("RobustCallBack", "logNotify log: " + log);
Log.d("RobustCallBack", "logNotify where: " + where);
}
@Override
public void exceptionNotify(Throwable throwable, String where) {
Log.e("RobustCallBack", "exceptionNotify where: " + where, throwable);
}
}
在這裏要說一下
RobustCallBack 只是一個對是否成功加載補丁包的一個回調 你在這裏可以看到加載補丁包的成功或者錯誤的信息
完成以上的步驟基本的配置就算是完成了 接下來就是如何打release包和打補丁了
按照平時打包的時候的流程
在生成 apk 的時候使用 apply plugin:'robust',該插件會生成打補丁時需要的方法記錄文件 methodMap.robust,該文件在打補丁的時候用來區別到底哪些方法需要被修復,所以有它才能打補丁。而上文所說的還有 mapping.txt 文件,該文件列出了原始的類,方法和字段名與混淆後代碼間的映射。這個文件很重要,可以用它來翻譯被混淆的代碼。但也不是必須的,如果不需要混淆,可以不保留。這兩個文件在生成apk後,分別在 build/outputs/robust/methodsMap.robust,build/outputs/mapping/mapping.txt(需要開啓混淆後纔會出現),在 app 目錄新建個叫 robust 的文件夾,我們需要自己分別拷貝到 app/robust 下,
接下來就是如何打補丁jar包了
這個時候你可以隨便修改下你的 代碼讓補丁包和上面打出的包內容有所改動,
比如你在這裏要新增一個方法 或者類,你就需要在新增的方法或者類 上面加一個
@Add 註解
修改代碼,在改動的方法上面添加@Modify
註解,對於Lambda表達式請在修改的方法裏面調用RobustModify.modify()方法
@Modify
做完上面的修改之後接下來就是如何打補丁包了,首先修改app下面的build.gradle
然後重複打包步驟 等一小會之後會提示你打包失敗但是這個時候你要去看具體的錯誤信息 如果你看到如下信息就證明打包成功了
打出來的補丁包在這裏
到這一步基本上都已經完成了所有的步驟了
你現在可以把你第一次打的包安裝到你的測試機上看下加載補丁包之前的樣子,然後 你需要把補丁包放到如下的路徑裏,當然你也可以自定義路徑
然後你再回到你的app裏面 去點擊某個按鈕去加載你的補丁jar 包 這個時候 你之前隨便修改的內容就會替換掉原來的內容或者不存在的內容
到這裏基本上所有的步驟都已經完成 如有疑問可以加我個人QQ1778934152 一塊探討學習