热修复 RocooFix篇(一)

吐槽之前先放一张大帅图.

(md 这张图貌似有点小 不纠结这个了==)

有时候项目刚刚上线或者迭代 测试或者在线上使用测出一个bug来 真让人蛋疼 不得不重新改bug测试 打包混淆上线感觉就向findviewById一样让你无法忍受

热修复从15年开始火起来 关于热修复的理论知识基于QQ空间热修复的一片文章(后面我会附上这几天学习的了解 不想看吐槽的可以滑到最后面 没办法为了凑字

数不够150个字数不允许发表 难道这就可以阻挡我吐槽的 呸 是学习的热情了吗)

其实在学习热修复之前 我们还是有必要了解一下热修复的原理 下面开始正经(后面均有链接):

热修复大致分为两种解决方式:

PathClassLoader:

  

DexClassLoader:

  

官方文档说的很明白了:(我也没看明白 接着查==)

参考一下stack overflow的回答:

两者的区别PathClassLoader只能加载本地的classes 而DexClassLoader可以加载apk或者jar压缩包中的dex文件

需要注意的是:

就是说DexClassLoader不提倡从sd卡加载 ,(This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getCodeCacheDir() to create such a directory: 需要私人的,可写的目录缓存优化类 也是一些热修复一些生成jar 需要md5加密的原因?)关于这个问题 农民伯伯11年就写了一篇博文:

Android动态加载jar/dex :http://www.cnblogs.com/over140/archive/2011/11/23/2259367.html

以及这位 后来没坚持写博客了 http://blog.csdn.net/quaful/article/details/6096951

(这里忍不住吐槽一下: 我靠 农名伯伯是有多屌啊!可惜不能发表情包 吓得我下巴就要掉下来了!应用这么多图和链接 只是帮助我们了解一下 现在大部分博文不严谨 百度一下全是一样的。写到这里感觉 如果接着写下去的话 这个题目要改为热修复初探! 当然不! 分享才知道自己的不足)

...关于热修复 目前大概有以下几种(不严谨 有错误欢迎补充 谢谢. 关于以下几种分别附上链接 以及 demo个人使用总结 部分还在学习中):

基于Xposed的AOP框架的淘宝的 Dexposed,支付宝的AndFix,QZone的超级热补丁方案(Nuwa 采用了相同的方式),以及微信的Tinker,

RocooFix(是基于扣扣空间的方案,稳不稳定看下面微信对qq空间方案的评价 本篇后面使用Tomat 完整演示),饿了么的Amigo,以及掌阅的Zeus Plugin等等吧。

================以上内容 是初探 ===========================

关于RocooFix

在使用RocooFix之前 我们很快找到两种方法:

静态修复:

RocooFix.applyPatch(Context context, String dexPath);

动态修复:

RocooFix.applyPatchRuntime(Context context, String dexPath);   

思路:

    出现bug之后 我们使用RocooFix集成 生成patch.jar文件 给后台让其上传到服务器(获取向后台要三个接口 一个上传patch.jar文件 一个用来修改json数据

一个用来获取到json数据)

  静态修复的话 我们 直接从服务器拿到jar数据 放在sdcard某个位置 app重启自动修复

  动态修复的话 我们可能要多思考几步 就是动态修复完成之后 如何避免重复 下载 需要json权限判断 (代码中会具体有)

  如果同时使用静态修复和动态修复的话 可能会崩溃

Tomcat 解压之后在webapps目录下新建文件夹(我的是HotFix) .复制\ROOT目录下 WEB-INF,丢进HotFix中。

patch.jar是我们在第二次编译Android Studio version 2 debug目录下生成的jar文件 我们复制到HotFix 目录下

并且新建b.txt文件 存储json字符串 下载到本地:

{"md5":"json","patch":"10.0.2.2/HotFix/patch.jar","Root":"wifi","download":"ok"}

md5 用于txt文本加密 patch模拟服务器patch.jar生成的位置 root 个人感觉是否需要判断 在wifi条件下自动修复 download 本地下载txt文件之后 判断是否是ok 如果ok 动态修复 修复之后 将ok 的值改为其他值 覆盖sdcard的txt文件 避免静态修复 之后启用动态修复 这样会崩掉

具体见代码:

  App文件:

package com.example.administrator.myapplication;

import android.app.Application;import android.content.Context;import android.os.Environment;import android.util.Log;

import com.dodola.rocoofix.RocooFix;

import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;

import okhttp3.Call;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response;

/** * Created by One on 2016/8/31. */public class App extends Application {

    private String path = "/One1988/data";//项目目录    private String urlTest = "http://10.0.2.2/HotFix/b.txt";//Tomcat上面的 text文件json数据    private String patchPath = Environment.getExternalStorageDirectory().getAbsolutePath() +            path + "/patch.jar";//jar文件 位于 sdcard/One1988/data 目录下

    @Override    protected void attachBaseContext(Context base) {        super.attachBaseContext(base);        RocooFix.init(base);

        try {            if (patchPath != null) {                File file = new File(patchPath);                if (file.exists()) {                    /**                     *  存在的话修复从制定目录加载静态修复                     */                    try {                        RocooFix.applyPatch(base, patchPath);                    } catch (Exception e) {                        Log.d("file.exist", "热修复出现异常: ");                    }                } else {                    /**                     * 没有的话下载写入 读取                     */                    String apath = Environment.getExternalStorageDirectory().getAbsolutePath() + path;                    File fileP = new File(apath);                    if(!fileP.getParentFile().exists()){                       fileP.getParentFile().mkdirs();                    }                    getHttp(urlTest);                }            }        } catch (Exception e) {            Log.d("RocooFix热修复出现问题", e.toString());        }    }

    /**     * 下载写入sdcard json数据 用于控制     */    OkHttpClient mOkHttpClient = new OkHttpClient();

    private void getHttp(String url) {

        Request request = new Request.Builder()                .url(url)                .build();

        mOkHttpClient.newCall(request).enqueue(new okhttp3.Callback() {

            @Override            public void onFailure(Call call, IOException e) {

            }

            @Override            public void onResponse(Call call, Response response) throws IOException {                if (response.isSuccessful()) {                    writeSdcard(response, "download.txt");//写入txt文件                }            }        });    }

    /**     * 写入txt文件进sdcard实际考虑加密什么的     * @param response     */    private void writeSdcard(Response response, String filePath) {        InputStream is = null;        byte[] buf = new byte[2048];        int len = 0;        FileOutputStream fos = null;        String SDPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/One1988/data";        try {            is = response.body().byteStream();            File file = new File(SDPath, filePath);            fos = new FileOutputStream(file);            long sum = 0;            while ((len = is.read(buf)) != -1) {                fos.write(buf, 0, len);                sum += len;            }            fos.flush();            Log.d("文件下载", "txt文件下载成功");        } catch (Exception e) {            Log.d("文件下载", "文件下载失败");        } finally {            try {                if (is != null)                    is.close();            } catch (IOException e) {            }            try {                if (fos != null)                    fos.close();            } catch (IOException e) {            }        }    }                                                                                                                                                                                                   }

MainActivity文件:package com.example.administrator.myapplication;
import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.widget.Button;import android.widget.Toast;

import com.dodola.rocoofix.RocooFix;import com.example.administrator.myapplication.bean.FileUtils;

import org.json.JSONObject;

import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;

import okhttp3.Call;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response;

public class MainActivity extends AppCompatActivity {

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

    /**     * 测试Ui     */    private String patchPath = Environment.getExternalStorageDirectory().getAbsolutePath()  + "/One1988/data/patch.jar";    private void initUi() {        Button button = (Button) findViewById(R.id.button);        Button buttonRun = (Button) findViewById(R.id.buttonRun);        final Test test = new Test();

        /**         * 测试立即生效         */        button.setOnClickListener(v -> {               Toast.makeText(MainActivity.this, test.show(), Toast.LENGTH_SHORT).show();        });

        /**         * button run测试         */        buttonRun.setOnClickListener(v -> {            try {                TestRun run = new TestRun();                String json = read();                JSONObject obj = new JSONObject(json);                String download = obj.optString("download");                Log.d("download字段", "json: " + json);                Log.d("download字段", "download: " + download);                if (download.equals("ok")) {                    //下载修复                    //getHttp(urlPatch);                    updateJson();

                } else {                    Toast.makeText(this, run.run(), Toast.LENGTH_SHORT).show();                }            } catch (Exception e) {                e.printStackTrace();            }        });    }

    /**     * 插件位于sdcard位置     */    private String urlPatch = "http://10.0.2.2/HotFix/patch.jar";

    /**     * 读取字符串     * @return     * @throws Exception     */    private String read() throws Exception {//读取sdcard中的json字符串        String SDPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/One1988/data";

        File file = new File(SDPath, "/download.txt");

        StringBuffer sb = new StringBuffer();

        FileInputStream fis = new FileInputStream(file);        int c;        while ((c = fis.read()) != -1) {            sb.append((char) c);        }        fis.close();        String fileString = sb.toString();        Log.d("FileInputStream:", "file: " + sb.toString());        return fileString;    }

    /**     * Test 测试     */    public class Test {

        public String show(){            return "出现bug!";            //return "来点不一样的!";        }    }

    /**     * 测试     */    public class TestRun {

        public String run(){            return "测试修复前!";            //return "bug已经修复了欧耶!";        }    }

    /**     *  下载jar文件立即修复     */    private void writeSdcard(Response response, String filePath) {        InputStream is = null;        byte[] buf = new byte[2048];        int len = 0;        FileOutputStream fos = null;        String SDPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/One1988/data";        try {            is = response.body().byteStream();            File file = new File(SDPath, filePath);            fos = new FileOutputStream(file);            long sum = 0;            while ((len = is.read(buf)) != -1) {                fos.write(buf, 0, len);                sum += len;            }            fos.flush();            Log.d("文件下载", "jar文件下载成功");            RocooFix.applyPatchRuntime(this, patchPath);            mHandler.sendEmptyMessage(0);        } catch (Exception e) {            Log.d("文件下载", "文件下载失败");        } finally {            try {                if (is != null)                    is.close();            } catch (IOException e) {            }            try {                if (fos != null)                    fos.close();            } catch (IOException e) {            }        }    }

    /**     * 下载     */    OkHttpClient mOkHttpClient = new OkHttpClient();

    private void getHttp(String url) {

        Request request = new Request.Builder()                .url(url)                .build();

        mOkHttpClient.newCall(request).enqueue(new okhttp3.Callback() {            @Override            public void onFailure(Call call, IOException e) {

            }

            @Override            public void onResponse(Call call, Response response) throws IOException {                if (response.isSuccessful()) {                    writeSdcard(response, "patch.jar");                }            }        });    }

    /**     * Handler     */    private Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            switch (msg.what){                case 0:                    TestRun run = new TestRun();                    Toast.makeText(MainActivity.this, run.run(), Toast.LENGTH_SHORT).show();

                    /**                     * 我的想法是:                     */                    try {                        String json = read();                        if(json.contains("ok")){                            json.replaceAll("ok","nohttp");                        }

                        File file = new File(patchPath);                        if(file.exists()){                            FileUtils.writeFileFromString(file,json,false);                        }                        Log.d("读取到的字符串:", "handleMessage: "+read());                    } catch (Exception e) {                        e.printStackTrace();                    }                    break;                default:                    break;            }        }    };

    /**     * 测试更改sdcard中的字符串     */    String download = Environment.getExternalStorageDirectory().getAbsolutePath() + "/One1988/data/download.txt";    private void updateJson() {        try {            String read = read();            Log.d("写入字符串", "之前: "+read);            /*Gson gson = new Gson();            Bean bean = gson.fromJson(read, Bean.class);            if(bean.getDownload().equals("ok")){                bean.setDownload("哇咔咔");            }            String write = gson.toJson(bean);*/

            /**             * 我尝试用上面方法修改json字符串但是 加入gson依赖之后             * 添加混淆会出错 这种比较麻烦用于学习 后面会学习一下其他的几种方式             * 选择一种最好的混淆方式 如果这里你解决了 请告诉我 谢谢!             */

            JSONObject object = new JSONObject();            object.put("md5","json");            object.put("patch","10.0.2.2/HotFix/patch.jar");            object.put("Root","wifi");            object.put("download","成功修改后!");            String write = String.valueOf(object);            Log.d("写入字符串", "之前: ======");            Log.d("写入字符串", "之前: "+write);            write(write,download);            Log.d("写入字符串", "之后: "+read());        } catch (Exception e) {            e.printStackTrace();        }    }

    /**     * 写入字符串到txt文件     * @param toSaveString     * @param filePath     */    public static void write(String toSaveString, String filePath) {        try{            File saveFile = new File(filePath);            if(!saveFile.exists()){                File dir = new File(saveFile.getParent());                dir.mkdirs();                saveFile.createNewFile();            }            FileOutputStream outputStream = new FileOutputStream(saveFile);            outputStream.write(toSaveString.getBytes());            outputStream.close();        }catch (Exception e){            Log.d("字符串写入失败", "saveFile: "+e.toString());        }    }

}其他配置 等具体见demo这种比较麻烦的是 考虑混淆问题 后面学习Amigo的使用比较几种热修复的优点。记录一下。  

QQ空间终端开发团队:(引发热潮的一篇文章 文中的图片我就不引用了 大神请绕道)

https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a

PathClassLoader和DexClassLoader官方文档:

https://developer.android.com/reference/dalvik/system/PathClassLoader.html

https://developer.android.com/reference/dalvik/system/DexClassLoader.html

stack overflow 上关于PathClassLoader和DexClassLoader的不同

http://stackoverflow.com/questions/37296192/what-are-differences-between-dexclassloader-and-pathclassloader

饿了么:https://github.com/eleme/Amigo

掌阅:https://github.com/iReaderAndroid/ZeusPlugin

女娲: https://github.com/jasonross/Nuwa

RocooFix:https://github.com/dodola/RocooFix

Tomact 8.5.4 windows 64:

http://download.csdn.net/detail/onebelowzero2012/9626103

demo 以及txt文件:

http://download.csdn.net/detail/onebelowzero2012/9628341

最后:欢迎给出意见 一起学习 加入群Android&Go,Let‘s go! 521039620 (感觉自己像个拉皮条的 ==)

时间: 2024-09-28 05:35:26

热修复 RocooFix篇(一)的相关文章

Android 热修复方案分析

绝大部分的APP项目其实都需要一个动态化方案,来应对线上紧急bug修复发新版本的高成本.之前有利用加壳,分拆两个dex结合DexClassLoader实现了一套全量更新的热更方案.实现原理在Android 基于Proxy/Delegate 实现bug热修复这篇博客中有分解.因为这套方案是在Java端实现,并且是全量更新所以兼容性较好,成功率较高.但是在线上跑了几个月之后就碰到了瓶颈,因为随着业务的增长分拆过之后的dex文件方法数也超过65535个,更换拆包方案的话维护成本太高.同时由于没有做差异

Android RocooFix 热修复框架

这里我要讲述下android热补丁前世今生 Under the Hood: Rebuilding Facebook for Android 发布者:Frank Qixing DU · 发布时间:2012年12月14日上午 3:01 Over the last year, we've been retooling our mobile apps to make them faster, more reliable, and easier to use. Several months ago, we

热修复-Nuwa学习篇

nuwa热修复是基于qq空间团队的思路,最近的热度话题了,很多种方案,自己先研究几种方案,基本上都各有优势,学习肯定得先挑个软柿子捏了,自己对比了一下,发现nuwa代码量少点,所以就决定了,先研究nuwa. 首先肯定得gradle 例子github上也都有,也可以下载别人的项目借鉴 然后进入application看看具体情况 跟着流程看下去 下面的方法才是重点 /** * 笔记 * "类加载器"(ClassLoader),其作用是动态装载Class文件,每个ClassLoader对象在

美团热修复Robust的踩坑之旅-使用篇

最近需要在项目中使用热修复框架,在这里以美团的Robust为主写一篇文章总结一下学习的过程. 一直认为要学习一个框架的原理,首先需要让他跑起来,从效果反推回去,这样更容易理解. 一.美团Robust的使用 首先我们在编写代码前需要做如下准备 配置文件读写权限,6.0以上需要手动获取 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>

android产品研发(七)--&gt;Apk热修复

转载请标明出处:一片枫叶的专栏 去年一整年android社区中刮过了一阵热修复的风,各大厂商,逼格大牛纷纷开源了热修复框架,恩,产品过程中怎么可能没有bug呢?重新打包上线?成本太高用户体验也不好,咋办?上热修复呗. 好吧,既然要开始上热修复的功能,那么就得调研一下热修复的原理.下面我将分别讲述一下热修复的原理,各大热修复框架的比较,以及自身产品中热修复功能的实践. 热修复的原理 通过更改dex加载顺序实现热修复 最新github上开源了很多热补丁动态修复框架,大致有: HotFix      

聊聊Android 热修复Nuwa有哪些坑

原创地址:http://blog.csdn.net/sbsujjbcy/article/details/51028027 前面写了两篇关于Nuwa的文章 Android 热修复Nuwa的原理及Gradle插件源码解析 Android 热修复使用Gradle Plugin1.5改造Nuwa插件 然后我说了Nuwa有坑,有人就问Nuwa到底有哪些坑,这篇文章对自己在Nuwa上走过的坑做一个总结,如果你遇到了其他坑,欢迎留言,我会统一加到文章中去.当然有些也不算是Nuwa的坑,算是ClassLoade

Android热修复:Andfix和Hotfix,两种方案的比较与实现

Andfix和hotfix是两种android热修复框架. android的热修复技术我看的最早的应该是QQ空间团队的解决方案,后来真正需要了,才仔细调查,现在的方案中,阿里有两种Dexposed和Andfix框架,由于前一种不支持5.0以上android系统,所以阿里系的方案我们就看Andfix就好.Hotfix框架算是对上文提到的QQ空间团队理论实现.本文旨在写实现方案,捎带原理. Andfix 引入 框架官网:https://github.com/alibaba/AndFix 介绍是用英文

Android 热补丁技术——资源的热修复

前言 今年真是热补丁框架的洪荒之力爆发的一年,短短几个月内,已经出现了好几个热修复的框架了,基本上都是大同小异,这里我就不过多的去评论这些框架.只有自己真正的去经历过,你才会发现其中的 大写的坑 事实上,现在出现的大多数热修复的框架,稳定性和兼容性都还达不到要求,包括阿里的Andfix,据同事说,我们自己的app原本没有多少crash,接入了andfix倒引起了一部分的crash,这不是一个热修复框架所应该具有的"变态功能".虽然阿里百川现在在大力推广这套框架,我依旧不看好,只是其思路

Android热修复原理普及

Android热修复原理普及 这段时间比较难闲,就抽空研究一下Android热修复的原理.自从Android热修复这项技术出现之后,随之而现的是多种热修复方案的出现.前两天又看到一篇文章分析了几种热修复方案的比较. 原文地址是:[Android热修复] 技术方案的选型与验证 看完这篇文章,有点汗颜.有这么多的热修复方案,并且他们之间的实现原理也不一样,各有优缺点. 然后在尼古拉斯_赵四的博客中看到几篇关于热修复的文章,对着这几篇文章撸了一番.大概的了解了热修复一种原理,其思路和QQ空间提出的安卓