技術(shù)介紹
| 技術(shù) | 介紹 |
|---|---|
| 熱更新 | 修改某處我們需要進行少量修改的地方或者Bug,修復(fù)快,時效性高 |
| 增量更新 | 原有app的基礎(chǔ)上只更新發(fā)生變化的地方,其余保持原樣 |
| 升級更新 | 在當(dāng)前的版本做了大的修改時,我們需要全部下載Apk進行升級 |
Robust的實現(xiàn)
流程:
1.集成 Robust,生成 apk。保存期間的混淆文件 mapping.txt,以及 Robust 生成記錄文件 methodMap.robust
2.使用注解 @Modify 或者方法 RobustModify.modify() 標(biāo)注需要修復(fù)的方法
3.開啟補丁插件,執(zhí)行生成 apk 命令,獲得補丁包 patch.jar
4.通過推送或者接口的形式,通知 app 有補丁,需要修復(fù) 5.加載補丁文件不需要重新啟動應(yīng)用
一、添加依賴:
1.添加 classpath 'com.meituan.robust:gradle-plugin:0.3.3'
classpath 'com.meituan.robust:auto-patch-plugin:0.3.3'到build.gradle中
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.meituan.robust:gradle-plugin:0.3.3'
classpath 'com.meituan.robust:auto-patch-plugin:0.3.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}}
2.添加 compile 'com.meituan.robust:robust:0.3.3'到app-build.gradle-dependencies中
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso- core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.0.0'
compile 'com.meituan.robust:robust:0.3.3'
testCompile 'junit:junit:4.12'}
3.添加兩種模式到app-build.gradle中,具體看注解
apply plugin: 'com.android.application'
apply plugin: 'auto-patch-plugin' //生成插件是打開
//apply plugin: 'robust'//生成Apk時打開
4.在app-build.gradle打開混淆,如果不打開混淆,編譯Apk時將不生成mipping.txt文件,Robust更新會報錯,找不到此文件,具體看后面
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.RoubstDemo
}
}
注意:程序需要簽名,不簽名會導(dǎo)致安裝Apk時提示文件損壞,簽名可參考此篇文章Android-熱修復(fù)技術(shù)之AndFix
二、添加robust.xml到app根目錄中,看注解:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<switch>
<!--true代表打開Robust,請注意即使這個值為true,Robust也默認只在Release模式下開啟-->
<!--false代表關(guān)閉Robust,無論是Debug還是Release模式都不會運行robust-->
<turnOnRobust>true</turnOnRobust>
<!--<turnOnRobust>false</turnOnRobust>-->
<!--是否開啟手動模式,手動模式會去尋找配置項patchPackname包名下的所有類,自動的處理混淆,然后把patchPackname包名下的所有類制作成補丁-->
<!--這個開關(guān)只是把配置項patchPackname包名下的所有類制作成補丁,適用于特殊情況,一般不會遇到-->
<!--<manual>true</manual>-->
<manual>false</manual>
<!--是否強制插入插入代碼,Robust默認在debug模式下是關(guān)閉的,開啟這個選項為true會在debug下插入代碼-->
<!--但是當(dāng)配置項turnOnRobust是false時,這個配置項不會生效-->
<!--<forceInsert>true</forceInsert>-->
<forceInsert>false</forceInsert>
<!--是否捕獲補丁中所有異常,建議上線的時候這個開關(guān)的值為true,測試的時候為false-->
<catchReflectException>true</catchReflectException>
<!--<catchReflectException>false</catchReflectException>-->
<!--是否在補丁加上log,建議上線的時候這個開關(guān)的值為false,測試的時候為true-->
<!--<patchLog>true</patchLog>-->
<patchLog>false</patchLog>
<!--項目是否支持progaurd-->
<proguard>true</proguard>
<!--<proguard>false</proguard>-->
<!--項目是否支持ASM進行插樁,默認使用ASM,推薦使用ASM,Javaassist在容易和其他字節(jié)碼工具相互干擾-->
<useAsm>true</useAsm>
<!--<useAsm>false</useAsm>-->
</switch>
<!--需要熱補的包名或者類名,這些包名下的所有類都被會插入代碼-->
<!--這個配置項是各個APP需要自行配置,就是你們App里面你們自己代碼的包名,
這些包名下的類會被Robust插入代碼,沒有被Robust插入代碼的類Robust是無法修復(fù)的-->
<packname name="hotfixPackage">
<name>com.ffcs.z.robustdemo</name>
</packname>
<!--不需要Robust插入代碼的包名,Robust庫不需要插入代碼,如下的配置項請保留,還可以根據(jù)各個APP的情況執(zhí)行添加-->
<exceptPackname name="exceptPackage">
</exceptPackname>
<!--補丁的包名,請保持和類PatchManipulateImp中fetchPatchList方法中設(shè)置的補丁類名保持一致( setPatchesInfoImplClassFullName("com.meituan.robust.patch.PatchesInfoImpl")),
各個App可以獨立定制,需要確保的是setPatchesInfoImplClassFullName設(shè)置的包名是如下的配置項,類名必須是:PatchesInfoImpl-->
<patchPackname name="patchPackname">
<name>com.ffcs.z.robustdemo</name>
</patchPackname>
<!--自動化補丁中,不需要反射處理的類,這個配置項慎重選擇-->
<noNeedReflectClass name="classes no need to reflect">
</noNeedReflectClass>
</resources>
注意:其中需要修改的為packname 、patchPackname 兩個包名

robust配置完成
三、基本應(yīng)用程序
需要開啟的權(quán)限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
1.MainActivity
package com.ffcs.z.robustdemo;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import com.meituan.robust.PatchExecutor;
public class MainActivity extends AppCompatActivity {
Button btn;
Button seconde;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
btn = (Button) findViewById(R.id.btn);
seconde= (Button) findViewById(R.id.btn_seconde);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new PatchExecutor(getApplicationContext(), new PatchManipulateImp(), new RobustCallBackSample()).start();
}
});
seconde.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this,SecondeActivity.class));
}
});
}}
聲明兩個按鈕,一個更新按鈕,一個跳轉(zhuǎn)有bug的頁面,其中需要注意的是更新所使用的方法為PatchExecutor,參數(shù)一、當(dāng)前上下文,參數(shù)二,關(guān)聯(lián)patch.jar的信息,參數(shù)三、回調(diào)
PatchManipulateImp .java
package com.ffcs.z.robustdemo;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import com.meituan.robust.Patch;
import com.meituan.robust.PatchManipulate;
import com.meituan.robust.RobustApkHashUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Created by mivanzhang on 17/2/27.
* We recommend you rewrite your own PatchManipulate class ,adding your special patch Strategy,in the demo we just load the patch directly
* Pay attention to the difference of patch's LocalPath and patch's TempPath
* We recommend LocalPath store the origin patch.jar which may be encrypted,while TempPath is the true runnable jar
* 我們推薦繼承PatchManipulate實現(xiàn)你們App獨特的A補丁加載策略,其中setLocalPath設(shè)置補丁的原始路徑,這個路徑存儲的補丁是加密過得,setTempPath存儲解密之后的補丁,是可以執(zhí)行的jar文件
*setTempPath設(shè)置的補丁加載完畢即刻刪除,如果不需要加密和解密補丁,兩者沒有啥區(qū)別 */
public class PatchManipulateImp extends PatchManipulate {
/***
* connect to the network ,get the latest patches
* l聯(lián)網(wǎng)獲取最新的補丁
* @param context
*
* @return
*/
@Override
protected List<Patch> fetchPatchList(Context context) {
//將app自己的robustApkHash上報給服務(wù)端,服務(wù)端根據(jù)robustApkHash來區(qū)分每一次apk build來給app下發(fā)補丁
//apkhash is the unique identifier for apk,so you cannnot patch wrong apk.
String robustApkHash = RobustApkHashUtils.readRobustApkHash(context);
Log.i("PatchManipulateImp","robustApkHash :" + robustApkHash);
//connect to network to get patch list on servers
//在這里去聯(lián)網(wǎng)獲取補丁列表
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是存儲原始的補丁文件,這個文件應(yīng)該是加密過的,TempPath是加密之后的,TempPath下的補丁加載完畢就刪除,保證安全性
//這里面需要設(shè)置一些補丁的信息,主要是聯(lián)網(wǎng)的獲取的補丁信息。重要的如MD5,進行原始補丁文件的簡單校驗,以及補丁存儲的位置,這邊推薦把補丁的儲存位置放置到應(yīng)用的私有目錄下,保證安全性
patch.setLocalPath(Environment.getExternalStorageDirectory().getPath()+ File.separator+"robust"+File.separator + "patch");
//setPatchesInfoImplClassFullName 設(shè)置項各個App可以獨立定制,需要確保的是setPatchesInfoImplClassFullName設(shè)置的包名是和xml配置項patchPackname保持一致,而且類名必須是:PatchesInfoImpl
//請注意這里的設(shè)置
patch.setPatchesInfoImplClassFullName("com.ffcs.z.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的私有目錄
patch.setTempPath(context.getCacheDir()+ File.separator+"robust"+File.separator + "patch");
Log.i("PatchManipulateImp","verifyPatch :" + 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 {
Log.i("PatchManipulateImp","copy :"+srcPath );
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;
}}
RobustCallBackSample.java
package com.ffcs.z.robustdemo;
import android.util.Log;
import com.meituan.robust.Patch;
import com.meituan.robust.RobustCallBack;
public class RobustCallBackSample implements RobustCallBack {
@Override
public void onPatchListFetched(boolean result, boolean isNet) {
Log.i("RobustCallBack", "onPatchListFetched result: " + result);
}
@Override
public void onPatchFetched(boolean result, boolean isNet, Patch patch) {
Log.i("RobustCallBack", "onPatchFetched result: " + result);
Log.i("RobustCallBack", "onPatchFetched isNet: " + isNet);
Log.i("RobustCallBack", "onPatchFetched patch: " + patch.getName());
}
@Override
public void onPatchApplied(boolean result, Patch patch) {
Log.i("RobustCallBack", "onPatchApplied result: " + result);
Log.i("RobustCallBack", "onPatchApplied patch: " + patch.getName());
}
@Override
public void logNotify(String log, String where) {
Log.i("RobustCallBack", "logNotify log: " + log);
Log.i("RobustCallBack", "logNotify where: " + where);
}
@Override
public void exceptionNotify(Throwable throwable, String where) {
Log.e("RobustCallBack", "exceptionNotify where: " + where, throwable);
}}
2.SecondeActivity.java 第二個界面 聲明了一個TextView控件
package com.ffcs.z.robustdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.meituan.robust.patch.annotaion.Modify;
public class SecondeActivity extends AppCompatActivity {
TextView t;
@Override
@Modify
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_seconde);
t= (TextView) findViewById(R.id.text);// t.setText("未修改");
t.setText("已經(jīng)修改");
}}
四、編譯生成Apk(PS:也就是有Bug(SecondeActivity.java中的TextView為“未修改”))
打開美團生成Apk模式
apply plugin: 'com.android.application'
//apply plugin: 'auto-patch-plugin' //生成插件是打開
apply plugin: 'robust'//生成Apk時打開
編譯指令:gradlew clean assembleRelease --stacktrace --no-daemon


創(chuàng)建app/robust文件夾 將outputs/release中的mipping.txt和outputs/robust中的methodsMap.robust文件考到app/robust文件下
mipping.txt:該文件列出了原始的類,方法和字段名與混淆后代碼間的映射。這個文件很重要,可以用它來翻譯被混淆的代碼
methodsMap.robust:該文件在打補丁的時候用來區(qū)別到底哪些方法需要被修復(fù),所以有它才能打補丁
五、生成補丁包
1.打開美團補丁模式 app-build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'auto-patch-plugin' //生成插件是打開
//apply plugin: 'robust'//生成Apk時打開
2.修改Bug

此處除了Modify還有Add等,有需要的可以了解一下
3.編譯程序


將更新包考到手機robust文件夾中即可
更新時請按更新按鈕
注意:手機需要手動開啟存儲權(quán)限,沒有開啟會報:未找到...../robust/patch.jar