由于项目中需要使用到美团的robust热更方案,所以写篇文章来记录一下使用步骤,以防忘记。官方提供的教程是将robust应用到APP项目中,我就先从app入手。
1、先在AS上面新建一个HotDemo APP,在App的build.gradle,加入如下依赖
//制作补丁时将这个打开,auto-patch-plugin紧跟着com.android.application
//apply plugin: 'auto-patch-plugin'
apply plugin: 'robust'
compile 'com.meituan.robust:robust:0.4.78'
2、在整个项目的build.gradle加入classpath
classpath 'com.meituan.robust:gradle-plugin:0.4.78'
classpath 'com.meituan.robust:auto-patch-plugin:0.4.78'
3、新建文件名为robust.xml(文件名不能修改)的文件,文件路径在app项目的根目录下,并将如下配置拷贝到文件中。
<?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>false</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.xy.demo</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.xy.demo</name>
</patchPackname>
<!--自动化补丁中,不需要反射处理的类,这个配置项慎重选择-->
<noNeedReflectClass name="classes no need to reflect">
</noNeedReflectClass>
</resources>
4、经robust.xml如下配置改为自己项目中需要插入代码的包名
<!--需要热补的包名或者类名,这些包名下的所有类都被会插入代码-->
<!--这个配置项是各个APP需要自行配置,就是你们App里面你们自己代码的包名,
这些包名下的类会被Robust插入代码,没有被Robust插入代码的类Robust是无法修复的-->
<packname name="hotfixPackage">
<name>com.xy.demo</name>
</packname>
5、新建类PatchManipulateImp,代码中都有详细注释,如下
package com.xy.demo;
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
*
* <br>
* Pay attention to the difference of patch's LocalPath and patch's TempPath
*
* <br>
* We recommend LocalPath store the origin patch.jar which may be encrypted,while TempPath is the true runnable jar
*<br>
*<br>
* 我们推荐继承PatchManipulate实现你们App独特的A补丁加载策略,其中setLocalPath设置补丁的原始路径,这个路径存储的补丁是加密过得,setTempPath存储解密之后的补丁,是可以执行的jar文件
* <br>
* setTempPath设置的补丁加载完毕即刻删除,如果不需要加密和解密补丁,两者没有啥区别
*/
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("123");
//we recommend LocalPath store the origin patch.jar which may be encrypted,while TempPath is the true runnable jar
//LocalPath是存储原始的补丁文件,这个文件应该是加密过的,TempPath是加密之后的,TempPath下的补丁加载完毕就删除,保证安全性
//这里面需要设置一些补丁的信息,主要是联网的获取的补丁信息。重要的如MD5,进行原始补丁文件的简单校验,以及补丁存储的位置,这边推荐把补丁的储存位置放置到应用的私有目录下,保证安全性
patch.setLocalPath(Environment.getExternalStorageDirectory().getPath()+ File.separator+"robust"+ File.separator + "patch");
//setPatchesInfoImplClassFullName 设置项各个App可以独立定制,需要确保的是setPatchesInfoImplClassFullName设置的包名是和xml配置项patchPackname保持一致,而且类名必须是:PatchesInfoImpl
//请注意这里的设置
patch.setPatchesInfoImplClassFullName("com.xy.demo.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");
//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;
}
}
6、初始化robust
private void initRobust(){
new PatchExecutor(this, new PatchManipulateImp(), new Callback()).start();
}
class Callback implements RobustCallBack {
@Override
public void onPatchListFetched(boolean result, boolean isNet, List<Patch> patches) {
Log.i("robust","robust arrived in onPatchListFetched");
}
@Override
public void onPatchFetched(boolean result, boolean isNet, Patch patch) {
Log.i("robust","robust arrived in onPatchFetched");
}
@Override
public void onPatchApplied(boolean result, Patch patch) {
Log.i("robust","robust arrived in onPatchApplied");
}
@Override
public void logNotify(String log, String where) {
Log.i("robust"," robust arrived in logNotify " + where);
}
@Override
public void exceptionNotify(Throwable throwable, String where) {
throwable.printStackTrace();
Log.i("robust"," robust arrived in exceptionNotify " + where);
}
}
7、执行gradle命令生成APK,将生成好的APK安装到手机中。
gradlew clean assembleRelease --stacktrace --no-daemon
8、将app根目录新建robust文件夹,并将app/build/outputs/robust/methodsMap.robust文件拷贝过去。
9、生成补丁,在代码有修改的方法上加入注解@Modify,如下:
@Modify
private String getData(){
String data = "";
for (int i=0;i<10;i++){
data+=(i+"aaaaaaaaaaaaaaa");
}
return data;
}
10、将App的build.gradle 改为如下后再执行刚刚的gradl命令生成补丁。
apply plugin: 'com.android.application'
//制作补丁时将这个打开,auto-patch-plugin紧跟着com.android.application
apply plugin: 'auto-patch-plugin'
//apply plugin: 'robust'
11、执行gradle命令后报错没关系,有如下信息表示补丁生成成功了。
12、将补丁app\build\outputs\robust\patch.jar拷贝到手机目录,目录位置为PatchManipulateImp代码中设置的位置
//这里面需要设置一些补丁的信息,主要是联网的获取的补丁信息。重要的如MD5,进行原始补丁文件的简单校验,以及补丁存储的位置,这边推荐把补丁的储存位置放置到应用的私有目录下,保证安全性
patch.setLocalPath(Environment.getExternalStorageDirectory().getPath()+ File.separator+"robust"+ File.separator + "patch");