目前能够实现热修复的方案很多,各个方案的优缺点这里也不在一一比较了,这篇文章主要针对第一次尝试集成美团点评基于Instant Run的热修复方案的开发者。

Robust的GitHub地址:

https://github.com/Meituan-Dianping/Robust 。


1.在APP的build.gradle,加入依赖:

apply plugin: 'com.android.application'
//apply plugin: 'auto-patch-plugin' //制作补丁时将这个打开
apply plugin: 'robust'  //生成apk时把这个打开

dependencies{
    compile 'com.meituan.robust:robust:0.3.2'
}

下面是我的项目

APP 的build.gradle,大家可以当做参考:

apply plugin: 'com.android.application'
//apply plugin: 'auto-patch-plugin'
apply plugin: 'robust'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"
    defaultConfig {
        applicationId "com.example.michael.robustdemo10"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    compile 'com.meituan.robust:robust:0.4.5'
}



2在整个项目的build.gradle加入classpath:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
         classpath 'com.meituan.robust:gradle-plugin:0.4.5'
         classpath 'com.meituan.robust:auto-patch-plugin:0.4.5'
   }
}

这个不用解释了吧。


3.在app的src同级路径下添加robust.xml文件,并对其进行配置:

robust.xml是从GitHub上下载的项目中找到的,里面内容如下:


<?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.example.michael.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.example.michael.robustdemo</name>
    </patchPackname>

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

    </noNeedReflectClass>
</resources>

大家可以直接把上面的

robust.xml复杂到app的src同级路径下,然后修改两次:packname,修改成你要进行热修复的包名;patchPackname,这个随便写,后面要用到。


4.下面是代码了,逻辑很简单,主activity有两个按钮,一个加载补丁,一个跳转SecondActivity。PatchManipulateImp类是用来获取补丁文件用的。

MainActivity.class:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    protected Button btn1;
    protected Button btn2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.activity_main);
        initView();
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.btn1) {
            new PatchExecutor(getApplicationContext(), new PatchManipulateImp(), new callBack()).start();
        } else if (view.getId() == R.id.btn2) {
            startActivity(new Intent(MainActivity.this,SecondActivity.class));
        }
    }

    private void initView() {
        btn1 = (Button) findViewById(R.id.btn1);
        btn1.setOnClickListener(MainActivity.this);
        btn2 = (Button) findViewById(R.id.btn2);
        btn2.setOnClickListener(MainActivity.this);
    }

    private class callBack implements 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) {

        }
    }
}



PatchManipulateImp.class:


public class PatchManipulateImp extends 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(Environment.getExternalStorageDirectory().getPath() + File.separator + "robust" + File.separator + "patch.jar");
        patch.setTempPath(Environment.getExternalStorageDirectory().getPath() + File.separator + "robust" + File.separator + "patch");
        patch.setPatchesInfoImplClassFullName("com.example.michael.robustdemo.PatchesInfoImpl");
        List patches = new ArrayList<Patch>();
        patches.add(patch);
        return patches;
    }

    @Override

    protected boolean verifyPatch(Context context, Patch patch) {
        return true;
    }

    @Override
    protected boolean ensurePatchExist(Patch patch) {
        return true;
    }
}



注意

:setPatchesInfoImplClassFullName必须要和你在 robust.xml中设置的patchPackname对应,然后后面加上.PatchesInfoImpl。

SecondActivity.class:

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        TextView textView = (TextView) findViewById(R.id.text);
        textView.setText(getString());
    }


    private String getString() {
        return "hello robust";
    }
}



然后在清单文件中配置

SecondActivity,不要忘记添加读写内存卡的权限:

<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE" />


5.前面的准备工作都完成了,打正式包,并生成mapping.txt文件和methodsMap.robust文件:

注意:打包前要开启混淆模式:build.gradle中设置 minifyEnabled true

网上提供的打包方法是使用命令:

gradlew clean  assembleRelease --stacktrace --no-daemon但我使用此命令一直失败,所以就使用传统的方式进行签名打包。
生成apk后,如下图所示,分别生成methodsMap.robust,mapping.txt(需要开启混淆后才会出现),我们需要自己分别拷贝到 app/robust 下,在 app 目录新建个叫 robust 的文件夹,把这两个文件放进去就 ok 了。

android SmartRouter 参数 android robust_Android热修复


拷贝到robust的文件夹下:

android SmartRouter 参数 android robust_补丁_02


6.我们得到了 apk ,mapping.txt,methodMap.robust ,有了它们我们再继续生成补丁patch.jar。

修改APP的build.gradle:

apply plugin: 'com.android.application'
apply plugin: 'auto-patch-plugin'
//apply plugin: 'robust'

修改SecondActivity.class:

public class SecondActivity extends AppCompatActivity {

//    @Override
//    protected void onCreate(@Nullable Bundle savedInstanceState) {
//        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_second);
//        TextView textView = (TextView) findViewById(R.id.text);
//        textView.setText(getString());
//    }

    /**
     * @Modify 注解表明该方法会被打补丁
     *
     */
    @Override
    @Modify
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        TextView textView = (TextView) findViewById(R.id.text);
//        textView.setText(getString());
        textView.setText(getInfo());
        textView.setTextColor(Color.RED);
    }

    /**
     *  @Add 注解表明该方法是新增的
     *  @return
     */
    @Add
    public String getInfo() {
        String msg = "";
        for (int i = 0; i < 40; i++) {
            msg += i + "\n";
        }
        return msg;
    }

    private String getString() {
        return "hello robust";
    }
}

上面的两次改动要和之前的进行比较。


7.再次执行打包操作,这次打包其实是为了生成patch.jar文件:

这次打包会失败,会抛出异常Java.lang.RuntimeException: auto patch end successfully。不用担心,说明补丁包生成成功。在outputs/robust/文件夹下会看到两个文件,patch.dex和patch.jar。 

android SmartRouter 参数 android robust_补丁_03


8.将outputs/robust/patch.jar补丁包,push到手机对应的/sdcard/robust/patch_temp.jar上:

命令:adb push E:\Word\RobustDemo11\app\build\outputs\robust\patch.jar /sdcard/robust/patch_temp.jar

注意:上面的命令中E:\Word\RobustDemo11是项目的路径。

出现这:[100%] /sdcard/robust/patch_temp.jar,恭喜你,push成功。


9.把apk包安装到手机上,点击跳转到第二个activity,内容不变,然后点击"加载补丁"按钮,再次进入第二个activity,发现内容已经改变了,好了,到此热修复已完成!