准备
-
在APP的
build.gradle
文件中,添加依赖:apply plugin: 'com.android.application' //制作补丁时将这个打开,auto-patch-plugin紧跟着com.android.application //apply plugin: 'auto-patch-plugin' apply plugin: 'robust' compile 'com.meituan.robust:robust:0.4.99'
-
在整个项目的
build.gradle
中加入buildscript { repositories { jcenter() } dependencies { classpath 'com.meituan.robust:gradle-plugin:0.4.99' classpath 'com.meituan.robust:auto-patch-plugin:0.4.99' } }
将
robust.xml
文件拷到src
同级目录:
-
实现
PatchManipulate.java
和RobustCallBack.java
两个接口-
PatchManipulate.java
说明:/** * Created by hedex on 16/6/20. */ public abstract class PatchManipulate { /** * 获取补丁列表 * * @param context * @return 相应的补丁列表 */ protected abstract List<Patch> fetchPatchList(Context context); /** * 验证补丁文件md5是否一致 * 如果不存在,则动态下载 * * @param context * @param patch * @return 校验结果 */ protected abstract boolean verifyPatch(Context context, Patch patch); /** * 努力确保补丁文件存在,验证md5是否一致。 * 如果不存在,则动态下载 * * @param patch * @return 是否存在 */ protected abstract boolean ensurePatchExist(Patch patch); }
特别说明:
patch.setLocalPath()
的字符串参数,必须跟robust.xml
配置的patchPackname
字段中的字符串一致,并且类名一定要是PatchesInfoImpl
-
RobustCallBack.java
方法说明:/** * Created by hedex on 17/1/22. */ public interface RobustCallBack { /** * 获取补丁列表后,回调此方法 * * @param result 补丁 * @param isNet 补丁 */ void onPatchListFetched(boolean result, boolean isNet, List<Patch> patches); /** * 在获取补丁后,回调此方法 * * @param result 结果 * @param patch 补丁 */ void onPatchFetched(boolean result, boolean isNet, Patch patch); /** * 在补丁应用后,回调此方法 * * @param result 结果 * @param patch 补丁 */ void onPatchApplied(boolean result, Patch patch); void logNotify(String log, String where); void exceptionNotify(Throwable throwable, String where); }
-
使用
-
基础包的预插桩
基础包的预插桩配置主要在
robust.xml
文件中配置,主要包括:标签 说明 turnOnRobust 是否打开Robust(只在Release模式下有效) forceInsert 是否强制插入代码。如果为true,则在debug模式下,也会插入代码 catchReflectException 是否捕获补丁中所有异常, (建议上线的时候这个开关的值为true) patchLog 是否在补丁加上log, (建议上线的时候这个开关的值为false) proguard 项目是否支持proguard,(如果与项目配置不一致,打包时会出错) useAsm 插桩是否使用ASM,否则使用javaassist (默认使用ASM,推荐使用ASM,Javaassist在容易和其他字节码工具相互干扰) forceInsertLambda 针对Java8级别的Lambda表达式,编译为private级别的javac函数,此时由开发者决定是否进行插桩处理 <packname name="hotfixPackage"> 需要插桩的包名,或者类名 <exceptPackname name="exceptPackage"> 不需要插桩的包名,或者类名 <patchPackname name="patchPackname"> 补丁的包名,(这里设置的包名,必须和PatchManipulateImp中fetchPatchList方法中设置的补丁类名保持一致) 准备就绪后,在安装或者输出APK时,Robust就会在
build/outputs/robust/
目录下输出methodsMap.robust
和mapping.txt
。这两个文件非常重要,在打包输出后一定要和APK一起保存。 -
打补丁包
在修改代码之前,需要确定需要打补丁的APK是哪个版本,并将对应的
methodsMap.robust
和mapping.txt
文件拷贝到app/robust
文件下。-
将APP的
build.gradle
中的加入//制作补丁时将这个打开,auto-patch-plugin紧跟着com.android.application apply plugin: 'auto-patch-plugin'
-
然后开始修改需要修改的代码:
- 支持方法,类的新增 ------------
@Add
或者RobustModify.modify()
- 方法的修改 ------------------------
@Modify
- 支持新增字段暂时在内测,可以通过新增类来实现
- 支持方法,类的新增 ------------
-
运行程序(安装,编译或者打APK操作都可以)
这次操作会失败,目的只是为了生成
patch.jar
文件,会抛出java.lang.RuntimeException: auto patch end successfully
-
下发补丁 && 补丁包加载
生成补丁包之后,然后就是如何将
patch.jar
下发到线上应用上,或者说,线上应用如何得到补丁包。在Demo演示阶段,是将patch.jar
直接push到手机上,但是线上我们就需要让应用自己将补丁包下载到本地,并自动加载补丁包。- 在应用冷启动时,需要检查是否有新的补丁包,如果有,则下载,之后,验证本地补丁包是否有效,并加载所有补丁包
- 在应用处于运行阶段,如果有补丁包更新,需要Push推送,提醒应用主动拉新补丁包,并加载新的补丁包
总结
优点
- 由于使用的ASM技术插桩,Classloader进行的类加载,所以高兼容,高稳定性
- 补丁可以实时加载,实时生效
- 支持ProGuard的混淆,内联,优化等操作
缺点
- 代码是侵入式的,会在原有的类中加入相关代码
- 会增大apk的体积,平均一个函数会比原来增加17.47个字节,10万个函数会增加1.67M
坑
目前0.4.99对最新的Gradle 6.5是不支持的(issue)
-
Gradle 3.6及以上的版本默认启用了R8,会将插入的changeQuickRedirect变量优化掉,需要在混淆文件proguard-rules.pro中加入以下代码:
-keepclassmembers class **{ public static com.meituan.robust.ChangeQuickRedirect *;}
-
对于方法的返回值是this的情况现在支持不好,比如builder模式, 但在制作补丁代码时,可以通过如下方式来解决,增加一个类来包装一下(如下面的B类)
method a(){ return this; }
改为
method a(){ return new B().setThis(this).getThis(); }
不支持构造方法的修复
不支持资源和SO的修复
使用时需要注意的点 (每一步的操作都不复杂,但是容易漏掉步骤)
- 打基础包时,如果需要在Debug插桩,需要将打开
robust.xml
中的forceInsert
设置成true - 打完基础包之后的
methodsMap.robust
和mapping.txt
需要与APK包一起保存,方便查找 - 打补丁包时:将对应的
methodsMap.robust
和mapping.txt
拷贝到app/robust
目录,打开apply plugin: 'auto-patch-plugin'
自动化打包插件
小结:
- 如果项目对热修复要求不高,只是需要更新一些Java层代码,并且考虑到稳定性,兼容性,Robust都是一个不错的热修复方案。
- 目前Robust方案,美团只是开源的代码热修复方案:前期的基础包插桩,以及补丁的自动化,对于补丁的加载策略并没有具体的方案,并没有像Sophix一样提供服务,只是预留了接口。具体的方案需要根据不同的需求自己实现
-
是让CI持续集成自动打补丁包(应该是不能交给CI去做) - 本地修改后,直接打补丁包,手动上传到服务器 (涉及到一个服务器的问题,需要一个服务器去管理我们的补丁包)
-
- 是否需要支持热更新,如果需要,服务器还需要提供push推送服务,当有新的补丁包时,需要推送给APP,让APP主动下载补丁,并加载。