android使用美团的robuts对app进行热修复

网上借鉴的对比图,也是最终尝试robust的原因,robust可以增加类和方法,demo未测试增加类.据说资源方便也在测试中了

android使用美团的robuts对app进行热修复

robuts修复原理:github地址 

1.在项目跟目录的build.gradle文件中增加以下代码

android使用美团的robuts对app进行热修复

2.在宿主程序(app目录)的build.gradle文件中添加一下代码

android使用美团的robuts对app进行热修复

//apply plugin: 'auto-patch-plugin'
生成补丁文件时取消代码注释,需要放在除com.android.application其他的plugin之前.

3.在app目录下(src同级目录)创建必要的robuts目录和robuts.xml文件

android使用美团的robuts对app进行热修复

robuts.xml 重点修改两个地方:1.packname 需要修复文件的目录;2.patchpackname:要与patchmaninpulate实现类中设置setpatchesInfoImplClassFullName方法参数中的包名一样,类名默认都是PathesInfoImpl

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <switch>
        <!--true代表打开Robust,请注意即使这个值为trueRobust也默认只在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下插入代码-->
        <!--但是当配置项turnOnRobustfalse时,这个配置项不会生效-->
        <!--<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,推荐使用ASMJavaassist在容易和其他字节码工具相互干扰-->
        <useAsm>true</useAsm>
        <!--<useAsm>false</useAsm>-->
    </switch>

    <!--需要热补的包名或者类名,这些包名下的所有类都被会插入代码-->
    <!--这个配置项是各个APP需要自行配置,就是你们App里面你们自己代码的包名,
    这些包名下的类会被Robust插入代码,没有被Robust插入代码的类Robust是无法修复的-->
    <packname name="hotfixPackage">
        <!--需要打补丁的包-->
        <!--<name>com.meituan</name>-->
        <!--<name>com.sankuai</name>-->
        <!--<name>com.dianping</name>-->
        <name>com.example.huangxiaoyu.hotfixtest</name>
    </packname>

    <!--不需要Robust插入代码的包名,Robust库不需要插入代码,如下的配置项请保留,还可以根据各个APP的情况执行添加-->
    <exceptPackname name="exceptPackage">
        <name>com.meituan.robust</name>
        <name>com.meituan.sample.extension</name>
    </exceptPackname>

    <!--补丁的包名,请保持和类PatchManipulateImpfetchPatchList方法中设置的补丁类名保持一致( setPatchesInfoImplClassFullName("com.meituan.robust.patch.PatchesInfoImpl")),
    各个App可以独立定制,需要确保的是setPatchesInfoImplClassFullName设置的包名是如下的配置项,类名必须是:PatchesInfoImpl-->
    <patchPackname name="patchPackname">
        <name>com.example.huangxiaoyu.hotfixtest.patch</name>
    </patchPackname>

    <!--自动化补丁中,不需要反射处理的类,这个配置项慎重选择-->
    <noNeedReflectClass name="classes no need to reflect">

    </noNeedReflectClass>
</resources>

以上准备工作完成后开始打包修复前的apk,案例apk代码值演示robuts热修复功能,代码简单.2个页面.A页面跳转B页面,A页面两个按钮.一个加载修复后的打包文件.一个跳转到B页面.B页面根据修复补丁前后显示内容不同

android使用美团的robuts对app进行热修复

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 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);
        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 要和robutspatchPackname节点里面的值保持一样并且存在的目录*/
        patch.setPatchesInfoImplClassFullName("com.example.huangxiaoyu.hotfixtest.patch.PatchesInfoImpl");
        List patches = new ArrayList<Patch>();
        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, List<Patch> patches) {
        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中加载,程序重启后修复内容失效.

android使用美团的robuts对app进行热修复

以上完成后执行打包命令:

gradlew clean assembleRelease --stacktrace --no-daemon

注:如果打包命令不能执行,需配置环境变量path,增加adb正确路径,打完包后会安装生成的apk文件到手机.并运行软件

补丁前完成

把下面对应的mapping.txt和methoodsmap.robuts文件复制到app目录下(src同级目录)下的robuts目录

android使用美团的robuts对app进行热修复                           复制后 android使用美团的robuts对app进行热修复

修改seondactivity,修改的方法或者类需要添加@modify注解,添加的类和方法需要添加@add注解,否则不能正常打补丁.

android使用美团的robuts对app进行热修复

在app的build.gradle文件里面取消注释auto-patch-plugin

android使用美团的robuts对app进行热修复

4.再次执行执行打包命令

gradlew clean assembleRelease --stacktrace --no-daemon*/

第二次次打包会抛出auto patch end successfully表示补丁已经生成,生异常日志如下:

android使用美团的robuts对app进行热修复

在如下目录找到patch.jar,吧这个文件push到patchmanipulateimp类中设置的路径中去,注意路径的地址,其实代码是设置一个文件的路径.并不是设置补丁文件的目录,需要把文件push到最后一个字符串的前一个目录级别中去

android使用美团的robuts对app进行热修复 本案例push文件的地址android使用美团的robuts对app进行热修复

push之后在打开的app中点击上面的hellword按钮加载补丁.然后在点击第二helloworld按钮,补丁就能看到B页面补丁后显示的内容:"修复后后后".

重启程序后会在application中启动加载补丁. 如果删除补丁文件,则程序重启后补丁失效.

完成修复

实际应用中可以根据当前的程序版本下载不同补丁包.每次补丁替换patch.jar文件.下载后加载一下补丁.下次程序再次启动会按照application中的设置自动加载补丁