Robust使用篇
Robust使用相对还是比较简单的.有一些坑可能官方文档里讲的不是那么详细,需要自己踏过去
使用方法
-
在project build.gradle 下buildscript -> dependencies 加入以下代码
dependencies { classpath 'com.android.tools.build:gradle:3.5.3' classpath 'com.meituan.robust:gradle-plugin:0.4.91' classpath 'com.meituan.robust:auto-patch-plugin:0.4.91' #当前最新版本为0.4.91 }
-
在app的build.gradle中加入以下代码,然后sync
apply plugin: 'com.android.application' //下面这个是制作补丁时使用,暂时先注释掉 //apply plugin: 'auto-patch-plugin' apply plugin: 'robust'
-
在app目录下创建robust目录(留着后面备用),创建robust.xml,加入以下代码,可以根据自己的项目进行配置,注释已经写得很明白了.
<?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.dsg.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.dsg.robustdemo</name> </patchPackname> <!--自动化补丁中,不需要反射处理的类,这个配置项慎重选择--> <noNeedReflectClass name="classes no need to reflect"> </noNeedReflectClass>
</resources>
```
-
打一个release包,将build -> outputs -> mapping -> mapping.txt 和build -> outputs ->apk -> release -> app-release.apk 和 将build -> outputs ->robust 下的文件 一起复制到我们之前创建的robust目录下.这时候我们反编译一下apk,看看robust都对我们做了什么操作
robust反编译apk.png
我们可以看到,多了一个ChangeQuickRedirect对象,每个方法都加上了PatchProxy.proxy方法,这个具体后面会讲,Robust就是通过这种方式来判断是加载patch中的方法还是使用原方法来实现热修复
-
然后修改我们的源码,将需要修改的bad code 加上@Modify注解,新增的方法或变量加入@Add注解,将app下的build.gradle中auto-patch-plugin 插件打开,记得一定要将robust插件注释掉,如下
apply plugin: 'auto-patch-plugin' //apply plugin: 'robust'
-
然后删掉build文件夹,重新打正式包,这时候我们遇到failed,少年人,不要慌.正常正常.
autoPatch.png
只要你遇到auto patch end successfully就表示已经将插件包打包成功了,如下图所示
-
将outputs下的patch.jar复制到我们自己创建的robust文件夹,这个patch.jar就是我们的补丁包,我们需要将这个放在服务器上,再适当的时候进行下发,我们模拟一下下发服务器的操作,我们可以通过adb push 将patch.jar 放到我们手机的的robust文件夹(这个文件夹可以自定义,需要和下面代码中的对应),然后新建如下类
package com.dsg.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
*
* <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.dsg.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");
//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;
}
}
-
这时候我们就已经相当于将patch.jar下发到手机中,并且加载代码已经准备好,接下来只需要加载一下patch.jar,就可以实现热修复,加载方法如下
new PatchExecutor(getApplicationContext(), new PatchManipulateImp(), new RobustCallBack() { @Override public void onPatchListFetched(boolean result, boolean isNet, List<Patch> patches) { } @Override public void onPatchFetched(boolean result, boolean isNet, Patch patch) { } @Override public void onPatchApplied(boolean result, Patch patch) { } @Override public void logNotify(String log, String where) { } @Override public void exceptionNotify(Throwable throwable, String where) { } }).start();
-
到现在为止,整个robust热修复就已经完成了.
总结一下robust的优缺点
- 优点
- 兼容性好,因为没有hook系统的函数,我们也不需要对jvm进行适配,还参考了instant run,所以兼容性很好
- 实时热修复,只要patch.jar下发,重新加载就可以使用新方法,不需要重启应用
- 缺点
- 增加包体积,因为每个方法都需要插入上面的代码
- 不支持so和资源替换,不过美团文档中说已经在内测中
网友评论