网上借鉴的对比图,也是最终尝试robust的原因,robust可以增加类和方法,demo未测试增加类.据说资源方便也在测试中了
robuts修复原理:github地址
1.在项目跟目录的build.gradle文件中增加以下代码
2.在宿主程序(app目录)的build.gradle文件中添加一下代码
//apply plugin: 'auto-patch-plugin'
生成补丁文件时取消代码注释,需要放在除com.android.application其他的plugin之前.
3.在app目录下(src同级目录)创建必要的robuts目录和robuts.xml文件
robuts.xml 重点修改两个地方:1.packname 需要修复文件的目录;2.patchpackname:要与patchmaninpulate实现类中设置setpatchesInfoImplClassFullName方法参数中的包名一样,类名默认都是PathesInfoImpl
xml version="1.0" encoding="utf-8"?>true false false true false true true name="hotfixPackage"> com.example.huangxiaoyu.hotfixtest name="exceptPackage"> com.meituan.robust com.meituan.sample.extension name="patchPackname"> com.example.huangxiaoyu.hotfixtest.patch name="classes no need to reflect">
以上准备工作完成后开始打包修复前的apk,案例apk代码值演示robuts热修复功能,代码简单.2个页面.A页面跳转B页面,A页面两个按钮.一个加载修复后的打包文件.一个跳转到B页面.B页面根据修复补丁前后显示内容不同
mainactivity主要代码
findViewById(R.id.btn_upload).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) {//加载补丁 new PatchExecutor(getApplicationContext(), new PatchManipulateImp(), new CallBack()).start(); } }); findViewById(R.id.btn_action).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(MainActivity.this, Activity_Second.class)); } });
secondactivity修复前
public class Activity_Second extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); TextView tv_text = findViewById(R.id.tv_text); tv_text.setText(getString()); } String getString() { return "修复前"; } }
patchmanipulateimp
public class PatchManipulateImp extends com.meituan.robust.PatchManipulate { @Override protected ListfetchPatchList(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); 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 patch.setLocalPath(context.getApplicationContext().getFilesDir().getAbsolutePath() + File.separator + "patch"); /*上面的路径看似设置的是目录,其实不是,在get方法中默认追加了.jar;temp默认则追加_temp.jar.可以理解为设置补丁的文件名.建议放在程序内部目录,提高安全性*/ /*com.example.huangxiaoyu.hotfixtest.patch 要和robuts中patchPackname节点里面的值保持一样并且存在的目录*/ patch.setPatchesInfoImplClassFullName("com.example.huangxiaoyu.hotfixtest.patch.PatchesInfoImpl"); List patches = new ArrayList (); patches.add(patch); return patches; } @Override protected boolean verifyPatch(Context context, Patch patch) { 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; } @Override protected boolean ensurePatchExist(Patch patch) { 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(); } } }
callback代码就是空实现
public class CallBack implements RobustCallBack { @Override public void onPatchListFetched(boolean result, boolean isNet, Listpatches) { Log.d("RobustCallBack", "onPatchListFetched result: " + result); Log.d("RobustCallBack", "onPatchListFetched isNet: " + isNet); for (Patch patch : patches) { Log.d("RobustCallBack", "onPatchListFetched patch: " + patch.getName()); } } @Override public void onPatchFetched(boolean result, boolean isNet, Patch patch) { Log.d("RobustCallBack", "onPatchFetched result: " + result); Log.d("RobustCallBack", "onPatchFetched isNet: " + isNet); Log.d("RobustCallBack", "onPatchFetched patch: " + patch.getName()); } @Override public void onPatchApplied(boolean result, Patch patch) { Log.d("RobustCallBack", "onPatchApplied result: " + result); Log.d("RobustCallBack", "onPatchApplied patch: " + patch.getName()); } @Override public void logNotify(String log, String where) { Log.d("RobustCallBack", "logNotify log: " + log); Log.d("RobustCallBack", "logNotify where: " + where); } @Override public void exceptionNotify(Throwable throwable, String where) { Log.e("RobustCallBack", "exceptionNotify where: " + where, throwable); } }
mineapplication中对补丁进行启动加载.测试中发现如果不在application中加载,程序重启后修复内容失效.
以上完成后执行打包命令:
gradlew clean assembleRelease --stacktrace --no-daemon
注:如果打包命令不能执行,需配置环境变量path,增加adb正确路径,打完包后会安装生成的apk文件到手机.并运行软件
补丁前完成
把下面对应的mapping.txt和methoodsmap.robuts文件复制到app目录下(src同级目录)下的robuts目录
修改seondactivity,修改的方法或者类需要添加@modify注解,添加的类和方法需要添加@add注解,否则不能正常打补丁.
在app的build.gradle文件里面取消注释auto-patch-plugin
4.再次执行执行打包命令
gradlew clean assembleRelease --stacktrace --no-daemon*/
第二次次打包会抛出auto patch end successfully表示补丁已经生成,生异常日志如下:
在如下目录找到patch.jar,吧这个文件push到patchmanipulateimp类中设置的路径中去,注意路径的地址,其实代码是设置一个文件的路径.并不是设置补丁文件的目录,需要把文件push到最后一个字符串的前一个目录级别中去
push之后在打开的app中点击上面的hellword按钮加载补丁.然后在点击第二helloworld按钮,补丁就能看到B页面补丁后显示的内容:"修复后后后".
重启程序后会在application中启动加载补丁. 如果删除补丁文件,则程序重启后补丁失效.
完成修复
实际应用中可以根据当前的程序版本下载不同补丁包.每次补丁替换patch.jar文件.下载后加载一下补丁.下次程序再次启动会按照application中的设置自动加载补丁