InstantRun
不得不說(shuō) InstantRun 真是個(gè)好東西顿苇。目前主流的熱修復(fù)框架都有或多或少的參考 InstantRun 的某些技術(shù)點(diǎn) 掷匠。
我們知道,InstantRun 對(duì)應(yīng)三種更新機(jī)制:
- 冷插拔岖圈,我們稱之為重啟更新機(jī)制
- 溫插拔,我們稱之為重啟Activity更新機(jī)制
- 熱插拔钙皮,我們稱之為熱更新機(jī)制
Robust 蜂科,其熱修復(fù)的關(guān)鍵技術(shù)點(diǎn)就是采用了 InstantRun 中的熱更新機(jī)制,對(duì)應(yīng)于多 ClassLoader 的動(dòng)態(tài)加載方案短条,即一個(gè) dex 文件對(duì)應(yīng)一個(gè)新建 ClassLoader 导匣。
優(yōu)勢(shì)
- 支持Android2.3-7.X版本
- 高兼容性、高穩(wěn)定性茸时,修復(fù)成功率高達(dá)三個(gè)九
- 補(bǔ)丁下發(fā)立即生效贡定,不需要重新啟動(dòng)
- 支持方法級(jí)別的修復(fù),包括靜態(tài)方法
- 支持增加方法和類
- 支持ProGuard的混淆可都、內(nèi)聯(lián)缓待、優(yōu)化等操作
大概流程
集成方法
- 在App的build.gradle,加入如下依賴
apply plugin: 'com.android.application'
//制作補(bǔ)丁時(shí)將這個(gè)打開(kāi)渠牲,auto-patch-plugin緊跟著com.android.application
//apply plugin: 'auto-patch-plugin'
apply plugin: 'robust'
compile 'com.meituan.robust:robust:0.4.2'
- 在整個(gè)項(xiàng)目的build.gradle加入classpath
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.meituan.robust:gradle-plugin:0.4.2'
classpath 'com.meituan.robust:auto-patch-plugin:0.4.2'
}
}
- 項(xiàng)目發(fā)release版本,把簽名文件在gradle中寫好
signingConfigs {
debug {
storeFile file("../robust.jks")
storePassword "123456"
keyAlias "robust"
keyPassword "123456"
}
release {
storeFile file("../robust.jks")
storePassword "123456"
keyAlias "robust"
keyPassword "123456"
}
}
4.開(kāi)啟proguard混淆開(kāi)關(guān)旋炒,robust做了混淆文件和代碼的映射。
5.將robust項(xiàng)目源碼中的app/src/robust.xml拷貝到自己項(xiàng)目下的app/src路徑下
打開(kāi)robust.xml修改兩個(gè)地方
6.執(zhí)行命令
./gradlew clean assembleRelease --stacktrace --no-daemon
7.app/build/outputs文件夾下會(huì)生成mapping.txt,methodsMap.robust文件签杈,將他們拷貝到app/robust文件夾中保存.
methodMap.robust瘫镇,該文件在打補(bǔ)丁的時(shí)候用來(lái)區(qū)別到底哪些方法需要被修復(fù),所以有它才能打補(bǔ)丁答姥。而上文所說(shuō)的還有 mapping.txt 文件铣除,該文件列出了原始的類,方法和字段名與混淆后代碼間的映射鹦付。
寫代碼
基本的配置就已經(jīng)配置好了尚粘,寫點(diǎn)簡(jiǎn)單的代碼,先看效果圖
功能很簡(jiǎn)單敲长,第二個(gè)按鈕跳轉(zhuǎn)到第二個(gè)Activity背苦。第一個(gè)按鈕加載一會(huì)兒生成的patch文件。跳轉(zhuǎn)代碼如下
findViewById(R.id.loaddPatch).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//關(guān)鍵是第二個(gè)參數(shù)
new PatchExecutor(getApplicationContext(), new PatchManipulateImp(), new Callback()).start();
}
});
findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,RobustActivity.class));
}
});
看下類PatchManipulateImp的代碼潘明,代碼是從官方拷貝過(guò)來(lái)的行剂,很簡(jiǎn)單。大致看一下
public class PatchManipulateImp extends PatchManipulate {
/***
* connect to the network ,get the latest patches
* l聯(lián)網(wǎng)獲取最新的補(bǔ)丁
* @param context
*
* @return
*/
@Override
protected List<Patch> fetchPatchList(Context context) {
//將app自己的robustApkHash上報(bào)給服務(wù)端钳降,服務(wù)端根據(jù)robustApkHash來(lái)區(qū)分每一次apk build來(lái)給app下發(fā)補(bǔ)丁
//apkhash is the unique identifier for apk,so you cannnot patch wrong apk.
//String robustApkHash = RobustApkHashUtils.readRobustApkHash(context);
//connect to network to get patch list on servers
//在這里去聯(lián)網(wǎng)獲取補(bǔ)丁列表
Patch patch = new Patch();
patch.setName("123");
//we recommend LocalPath store the origin patch.jar which may be encrypted,while TempPath is the true runnable jar
//LocalPath是存儲(chǔ)原始的補(bǔ)丁文件厚宰,這個(gè)文件應(yīng)該是加密過(guò)的,TempPath是加密之后的,TempPath下的補(bǔ)丁加載完畢就刪除铲觉,保證安全性
//這里面需要設(shè)置一些補(bǔ)丁的信息澈蝙,主要是聯(lián)網(wǎng)的獲取的補(bǔ)丁信息。重要的如MD5撵幽,進(jìn)行原始補(bǔ)丁文件的簡(jiǎn)單校驗(yàn)灯荧,以及補(bǔ)丁存儲(chǔ)的位置,這邊推薦把補(bǔ)丁的儲(chǔ)存位置放置到應(yīng)用的私有目錄下盐杂,保證安全性
patch.setLocalPath(Environment.getExternalStorageDirectory().getPath()+ File.separator+"robust"+File.separator + "patch");
//setPatchesInfoImplClassFullName 設(shè)置項(xiàng)各個(gè)App可以獨(dú)立定制逗载,需要確保的是setPatchesInfoImplClassFullName設(shè)置的包名是和xml配置項(xiàng)patchPackname保持一致,而且類名必須是:PatchesInfoImpl
//請(qǐng)注意這里的設(shè)置
patch.setPatchesInfoImplClassFullName("cx.com.robustdemo.PatchesInfoImpl");
List 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的私有目錄链烈,拷貝到自己的目錄下厉斟。執(zhí)行。
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("copy source patch to local patch error, no patch execute in path "+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;
}
}
這里一定要注意
//后綴必須是PatchesInfoImpl 强衡,前面的要和robust.xml中<package>節(jié)點(diǎn)下的包名對(duì)應(yīng)
patch.setPatchesInfoImplClassFullName("cx.com.robustdemo.PatchesInfoImpl");
繼承了PatchManipulate擦秽,有3個(gè)方法。
@Override
fetchPatchList() 方法主要是設(shè)置patch的路徑等配置信息漩勤,然后返回
@Override
verifyPatch() //放到app的私有目錄感挥,是加載的真正的路徑
@Override
protected boolean ensurePatchExist(Patch patch):
生成patch包
小小修改下第二個(gè)Activity的代碼!
執(zhí)行剛才的打包命令
./gradlew clean assembleRelease --stacktrace --no-daemon
命令跑到中間會(huì)錯(cuò),如下圖,但是看到patch包已經(jīng)生成了。
生成路徑如下圖
執(zhí)行命令
adb push app/build/outputs/robust/patch.jar /sdcard/robust
最后一步越败,點(diǎn)擊第一個(gè)按鈕链快,再點(diǎn)擊跳轉(zhuǎn)到第二個(gè)按鈕。先看效果眉尸,會(huì)發(fā)下hello robust 變?yōu)闊岣恕?/p>
點(diǎn)擊加載patch文件按鈕
剛剛增加的代碼打出的log,熱更新成功了域蜗。
不足
- 每次都要手動(dòng)拷貝map文件,每次的都要替換插件來(lái)生成patch包噪猾。