android 世面上的热更新方案有很多,例如QQ超级补丁 QZone 微信补丁方案 TinKer 阿里的AndFix 和美团的Robust 具体他们的优缺点网上一大堆 感兴趣的可以去深入了解一下 在这里就不一一说了,这里主要说的是美团的Robust的集成步骤

1.在项目最外层的 build.gradle 添加两处插件

classpath 'com.meituan.robust:gradle-plugin:0.4.91'
classpath 'com.meituan.robust:auto-patch-plugin:0.4.91

2.在项目App下面的 build.gradle 添加如下依赖

//制作补丁时将这个打开,auto-patch-plugin紧跟着com.android.application
//  apply plugin: 'auto-patch-plugin'
    apply plugin: 'robust'


dependencies {
implementation 'com.meituan.robust:robust:0.4.91'
}

 3.在App文件夹下创建 robust.xml 文件 内容可以copy 下面的内容 具体的设置都有很详细的注释 

<?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.meituan</name>
        <name>com.sankuai</name>
        <name>com.dianping</name>
        <name>com.lysoft.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.meituan.robust.patch</name>-->
        <name>com.lysoft.robustdemo</name>
    </patchPackname>

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

    </noNeedReflectClass>
</resources>

接下来你要先把如何加载补丁的逻辑 写出来 我这里是点击按钮去加载手机SD卡里面我提前放好的补丁jar包

new PatchExecutor(getApplicationContext(), new PatchManipulateImp(), new RobustCallBackSample()).start();
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("Robust补丁1");
        //we recommend LocalPath store the origin patch.jar which may be encrypted,while TempPath is the true runnable jar
        //LocalPath是存储原始的补丁文件,这个文件应该是加密过的,TempPath是加密之后的,TempPath下的补丁加载完毕就删除,保证安全性
        //这里面需要设置一些补丁的信息,主要是联网的获取的补丁信息。重要的如MD5,进行原始补丁文件的简单校验,以及补丁存储的位置,这边推荐把补丁的储存位置放置到应用的私有目录下,保证安全性
//        String localPath=Environment.getExternalStorageDirectory().getPath()+ File.separator+"0Rotbust"+File.separator + "patch";
        String localPath=Environment.getExternalStorageDirectory().getPath()+ File.separator+"0Rotbust"+ File.separator + "patch";
        patch.setLocalPath(localPath);
//        patch.setTempPath(localPath);
        Log.w("robust","localPath :" + localPath);

        //setPatchesInfoImplClassFullName 设置项各个App可以独立定制,需要确保的是setPatchesInfoImplClassFullName设置的包名是和xml配置项patchPackname保持一致,而且类名必须是:PatchesInfoImpl
        //请注意这里的设置
        patch.setPatchesInfoImplClassFullName("com.lysoft.robustdemo.PatchesInfoImpl");
        List<Patch>  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("将源修补程序复制到本地修补程序时出错,路径中没有执行修补程序 "+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;
    }
}

 

public class RobustCallBackSample 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);
    }
}

在这里要说一下 


RobustCallBack 只是一个对是否成功加载补丁包的一个回调 你在这里可以看到加载补丁包的成功或者错误的信息


完成以上的步骤基本的配置就算是完成了 接下来就是如何打release包和打补丁了

按照平时打包的时候的流程

安卓热更新失败ios更新成功 安卓热更新方案_安卓热更新失败ios更新成功

在生成 apk 的时候使用 apply plugin:'robust',该插件会生成打补丁时需要的方法记录文件 methodMap.robust,该文件在打补丁的时候用来区别到底哪些方法需要被修复,所以有它才能打补丁。而上文所说的还有 mapping.txt 文件,该文件列出了原始的类,方法和字段名与混淆后代码间的映射。这个文件很重要,可以用它来翻译被混淆的代码。但也不是必须的,如果不需要混淆,可以不保留。这两个文件在生成apk后,分别在 build/outputs/robust/methodsMap.robustbuild/outputs/mapping/mapping.txt(需要开启混淆后才会出现),在 app 目录新建个叫 robust 的文件夹,我们需要自己分别拷贝到 app/robust 下,

安卓热更新失败ios更新成功 安卓热更新方案_热更新_02

接下来就是如何打补丁jar包了

这个时候你可以随便修改下你的 代码让补丁包和上面打出的包内容有所改动,

比如你在这里要新增一个方法 或者类,你就需要在新增的方法或者类 上面加一个


@Add 注解


修改代码,在改动的方法上面添加@Modify注解,对于Lambda表达式请在修改的方法里面调用RobustModify.modify()方法


@Modify


安卓热更新失败ios更新成功 安卓热更新方案_android _03


 


做完上面的修改之后接下来就是如何打补丁包了,首先修改app下面的build.gradle 

 

 

安卓热更新失败ios更新成功 安卓热更新方案_安卓热更新失败ios更新成功_04

 然后重复打包步骤 等一小会之后会提示你打包失败但是这个时候你要去看具体的错误信息 如果你看到如下信息就证明打包成功了

安卓热更新失败ios更新成功 安卓热更新方案_android _05

打出来的补丁包在这里

安卓热更新失败ios更新成功 安卓热更新方案_rebust_06

 

到这一步基本上都已经完成了所有的步骤了 

你现在可以把你第一次打的包安装到你的测试机上看下加载补丁包之前的样子,然后 你需要把补丁包放到如下的路径里,当然你也可以自定义路径 

安卓热更新失败ios更新成功 安卓热更新方案_安卓热更新失败ios更新成功_07

 然后你再回到你的app里面 去点击某个按钮去加载你的补丁jar 包 这个时候 你之前随便修改的内容就会替换掉原来的内容或者不存在的内容