Android热更新方案Robust

http://tech.meituan.com/android_robust.html

Android热更新方案Robust开源,新增自动化补丁工具

http://tech.meituan.com/android_autopatch.html

美团 Robust 的 github demo 地址

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

Robust 的原理

Robust插件对产品的每个函数在编译打包阶段都插入了一段代码。当我们需要对已上线的app进行bug代码修复时,这时如果存在patch.jar,就会调用patch.jar中作为修复bug的代码而跳过原先的代码片段,由此达到修复的目的;而对产品的每个函数进行插入一段代码的工作是由插件 apply plugin : 'robust' 来完成的;对我们需要修复代码操作而实现修复功能的 patch.jar 是由插件 apply plugin : 'auto-patch-plugin' 生成的。

大致应用流程

 
/*** 1.集成了 Robust 后,生成 apk。保存期间的混淆*   文件 mapping.txt + Robust 生成记录文件 methodMap.robust ;* 2.使用注解 @Modify 和 @Add 标注需要修复和用*于修复的方法 ;* 3.开启补丁插件(apply plugin:'auto-patch-plugin'),*   执行生成 apk 命令,获得补丁包patch.jar ;* 4.通过推送或者接口的形式,通知 app 有补丁,需要修复;* 5.加载补丁文件不需要重新启动应用(即时生效)。*/

制作补丁前准备一

配置方面有两个地方:1.是在project级别的配置;2.是在module级别的配置;3.robust.xml复制到工程目录下;把如下图所示  

 

project级别的配置

 

module级别的配置

 

robust.xml配置

   

制作补丁前准备二

首先了解一下这个测试demo的逻辑;打开APP进入到MainActivity,在MainActivity中主要有两个按钮:点击进入HotFixActivity页面和点击执行修复功能;在HotFixActivity页面只有一个TextView显示文字,当可以进行代码修复功能时,在MainActivity点击修复按钮之后再点击进入HotFixActivity页面,你会发现TextView显示的内容变成了我们要修复的内容。 看一下代码:
package com.draem.application20170516;import android.content.Intent;import android.os.Bundle;import android.os.Environment;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;import com.meituan.robust.Patch;import com.meituan.robust.PatchExecutor;import com.meituan.robust.RobustCallBack;import java.io.File;import butterknife.BindView;import butterknife.ButterKnife;import butterknife.OnClick;public class MainActivity extends AppCompatActivity {    @BindView(R.id.btn_go)    Button btnGo;    @BindView(R.id.btn_hotfix)    Button btnHotfix;    @BindView(R.id.tvShow2)    TextView tvShow2;    @BindView(R.id.tvShow3)    TextView tvShow3;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);//        SystemClock.sleep(2*1000);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);        File file = getDir();//创建文件夹  /mnt/sdcard/HotFix    }    @OnClick({R.id.btn_go, R.id.btn_hotfix})    public void onClick(View view) {        switch (view.getId()) {            case R.id.btn_go:                Intent it = new Intent(MainActivity.this, HotFixActivity.class);                startActivity(it);                break;            case R.id.btn_hotfix://执行修复                new PatchExecutor(getApplicationContext(),                        new PatchManipulateImp(),                        new RobustCallBack() {                            @Override                            public void onPatchListFetched(boolean result, boolean isNet) {                                Log.e("error-hot", "打印 onPatchListFetched:" + "isNet=" + isNet );                            }                            @Override                            public void onPatchFetched(boolean result, boolean isNet, Patch patch) {                                Log.e("error-hot", "打印 onPatchFetched:" + "result=" + result+"isNet="+isNet + "--->" + "patch=" + patch);                            }                            @Override                            public void onPatchApplied(boolean result, Patch patch) {                                Log.e("error-hot", "打印 onPatchApplied:" + "result=" + result + "--->" + "patch=" + patch);                            }                            @Override                            public void logNotify(String log, String where) {                                Log.e("error-hot", "打印 logNotify:" + "log=" + log + "--->" + "where=" + where);                            }                            @Override                            public void exceptionNotify(Throwable throwable, String where) {                                Log.e("error-hot", "打印 exceptionNotify:" + "throwable=" + throwable.toString() + "--->" + "where=" + where);                            }                        }).start();                break;        }    }    int count = 0;    private File getDir() {        StringBuilder path = new StringBuilder();        if (isSDAvailable()) {            path.append(Environment.getExternalStorageDirectory()                    .getPath());            path.append(File.separator);// '/'            path.append("HotFix");// /mnt/sdcard/HotFix                      Log.e("error-hotfix", "如果SD卡可用就在SD卡创建");            tvShow3.setText("SD卡可用就在sd创建");        } else {            //如果SD卡不可用就在内存创建            File filesDir = getApplication().getCacheDir();    //  cache  getFileDir file            path.append(filesDir.getAbsolutePath());                       tvShow3.setText("SD卡不可用就在内存创建");            Log.e("error-hotfix", "SD卡不可用就在内存创建");        }        File file = new File(path.toString());        if (!file.exists() || !file.isDirectory()) {            file.mkdirs();// 创建文件夹            count += 10;        }        Toast.makeText(this, "file=" + file, Toast.LENGTH_SHORT).show();        Log.e("error-hotfix", count+" ==>file地址=" + file.toString() + "-->" + file.getAbsolutePath());        tvShow2.setText(file.toString()+ "\n" + file.getAbsolutePath());        return file;    }    private boolean isSDAvailable() {        if (Environment.getExternalStorageState().equals(                Environment.MEDIA_MOUNTED)) {            Toast.makeText(this, "sd 有效", Toast.LENGTH_SHORT).show();            return true;        } else {            return false;        }    }}
 
package com.draem.application20170516;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.TextView;import butterknife.BindView;import butterknife.ButterKnife;public class HotFixActivity extends AppCompatActivity {    @BindView(R.id.tvShow)    TextView tvShow;    @Override//    @Modify    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_hot_fix);        ButterKnife.bind(this);        tvShow.setText(getText());//加载错误的代码//        tvShow.setText(getInfo());//加载正确的代码    }    private String getText(){        return "Hot-Fix, this just an error";    }//    @Add//    public String getInfo(){//        return "Hot-Fix, 已经对含有error的代码进行了修改!";//    }}
 
package com.draem.application20170516;import android.content.Context;import android.os.Environment;import android.util.Log;import com.meituan.robust.Patch;import com.meituan.robust.PatchManipulate;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.util.ArrayList;import java.util.List;/** * Created by yuanjunhua on 2017/5/17. */public class PatchManipulateImp extends PatchManipulate {    @Override    protected List fetchPatchList(Context context) {        Patch patch = new Patch();        patch.setName("test patch");        StringBuilder path = new StringBuilder();        path.append(Environment.getExternalStorageDirectory()                .getPath());        path.append(File.separator);// '/'        path.append("HotFix");// /mnt/sdcard/HotFix        path.append(File.separator);        path.append("patch");// /mnt/sdcard/HotFix/patch        patch.setLocalPath(path.toString());        Log.e("error-hotfix", "PatchManipulateImp 地址="+path);        patch.setPatchesInfoImplClassFullName("com.draem.application20170516.PatchesInfoImpl");        List patchList = new ArrayList<>();        patchList.add(patch);        return patchList;    }    @Override    protected boolean verifyPatch(Context context, Patch patch) {        //do your verification, put the real patch to patch        //放到app的私有目录        StringBuilder path = new StringBuilder();        path.append(context.getCacheDir());        path.append(File.separator);// '/'        path.append("HotFix");// /mnt/sdcard/HotFix        path.append(File.separator);        path.append("patch");// /mnt/sdcard/HotFix/patch        patch.setTempPath(path.toString());        //in the sample we just copy the file        try {            Log.e("error-hotfix", "patch.getLocalPath="+patch.getLocalPath()+"--->patch.getTempPath="+patch.getTempPath());            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()){            try {                Log.e("error-hitfix", "资源不存在哦  srcPath="+srcPath);                Log.e("error-hitfix", "资源不存在哦  srcPath="+src.toString());                Log.e("error-hitfix", "资源不存在哦  srcPath="+src.length());            } catch (Exception e) {                e.printStackTrace();            }                        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();        }    }}

然后就保持上面的代码 + module级别gradle中保持设置
//在生成 apk 的时候使用 apply plugin:’robust’apply plugin: 'robust'开启打补丁那个,关闭生成apk那个//apply plugin: 'auto-patch-plugin'
这样准备二阶段就要告一段落了,然后我们执行终端命令生成apk:
 gradlew clean assembleRelease --stacktrace --no-daemon
  打包成功之后我们可以看到(并按照图示操作):         操作完之后,将上面的源代码该解注释的解注释该加注解的加上注解:         最后就是执行终端代码生成patch.jar:(打包apk会失败,没关系我们是为了生产jar)
gradlew clean assembleRelease --stacktrace --no-daemon
  到此为止,制作补丁前的准备已经完成;

开始修复工作

先要执行终端,把patch.jar文件copy到手机SD目录:/sdcard/HotFix/patch.jar
adb push C:\studio_workspace\Application20170516\app\build\outputs\robust\patch.jar /sdcard/HotFix/patch.jar
成功之后,我们只需要点击一下修复按钮然后跳转到HotFixActivity页面就好了,这时候会发现textview内容变化了!     还有就是在使用终端命令的时候可能会遇到“不是内部命令的错误”,解决如下:原Android Sdudio 2.2和AndroidSdudio 2.3不一样 ,Android Sdudio 2.3 的 adb.exe是放在android-sdk\platform-tools目录下面的,而2.2是放在tools目录下面的,所以需要把环境配置path的路径指到platform-tools下面。然后终端输入adb,能够显示相关的信息。当然如果你不想要使用终端命令来执行上面的操作完全可以使用“Build/Generate/Signed APK...”来代替上面的终端命令; 注意:1,测试安装到手机上的apk必须是签过名了的版本;2,配置SD卡的读写权限;    

更多相关文章

  1. Android很有用的代码片段
  2. SeekBar自定义
  3. android NDK环境搭建
  4. android settings命令
  5. android获取手机信息
  6. js 判断当前操作系统是ios还是android还是电脑端
  7. android中webview开启了硬件加速后会出现闪烁问题
  8. Android(安卓)和 PHP 之间进行数据加密传输
  9. android上传图片到服务器,求服务器那边和android的Activity的完整

随机推荐

  1. JKS密匙库专用格式,建议使用“keytool -im
  2. Android全屏及屏幕切换
  3. Android 开发常用开源库
  4. android core dump测试
  5. Android Studio 首次创建工程下载Gradle
  6. Android Phone和Pad UA区别
  7. Android学习系列(2)--App自动更新之通知
  8. Android 进度条算法 更新进度条算法 long
  9. 解决Eclipse New菜单没有Android Project
  10. 终于搞定Eclipse下看Android的源码