热修复 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 (感觉自己像个拉皮条的 ==)
=======2017/06/06 补记 现在回过头来看 感觉写的太浅 希望不要误导读者 自己多尝试 关于修复这块 建议大家用微信Tinker 或者 参考腾讯Bugly 的Tinker集成使用 谢谢大家。
热修复 RocooFix篇(一)的更多相关文章
- 美团热修复Robust-源码篇
上一篇主要分析了Robust的使用方法,这一篇就来总结一下Robust的源码分析. 我个人倾向于将Robust框架分为两个部分,自动插入代码和动态加载Patch. 一.Robust源码分析 目前我的分 ...
- 热修复-Nuwa学习篇
nuwa热修复是基于qq空间团队的思路,最近的热度话题了,很多种方案,自己先研究几种方案,基本上都各有优势,学习肯定得先挑个软柿子捏了,自己对比了一下,发现nuwa代码量少点,所以就决定了,先研究nu ...
- 《android基于andFix的热修复方案》思路篇
1:需求背景 项目上线之后,发现BUG需要修复(比如安卓兼容性等测试难以发现的问题),频繁的更新影响用户体验 2:方案要求 静默下载,耗费流量少,打完补丁后立刻生效,不用重启apk 3:解决思路 3. ...
- 美团热修复Robust的踩坑之旅-使用篇
最近需要在项目中使用热修复框架,在这里以美团的Robust为主写一篇文章总结一下学习的过程. 一直认为要学习一个框架的原理,首先需要让他跑起来,从效果反推回去,这样更容易理解. 一.美团Robust的 ...
- 关于iOS热修复(HotPatch)技术的使用总结
苹果做了非常多的努力来建造和维持一个健康并且干净的应用环境.其中对现在的现状起到很大作用的部分就是苹果APP STORE,它是被一个十分周密的对所有提交的应用进行检查的审批程序所保护的.尽管这个程序是 ...
- 使用jspatch进行热修复的实战总结
最近正式在线上项目中集成了jspatch进行热修复,这里做一个简单的总结. 工具篇: 首先,用xcode来编辑js非常困难,基本上没有缩进,完全需要手写:经过研究发现使用 Sublime text3 ...
- Android热修复之微信Tinker使用初探
文章地址:Android热修复之微信Tinker使用初探 前几天,万众期待的微信团队的Android热修复框架tinker终于在GitHub上开源了. 地址:https://github.com/ ...
- 关于JSPatch热修复
今天和同事聊到JSPatch热修复,我们来看下JSPatch,确实解决了APP升级的痛点. 刚好,已经有这么一个第三方平台实现了后台管理,全套服务(网址是:http://jspatch.com/),先 ...
- Android 热修复Nuwa的原理及Gradle插件源码解析
现在,热修复的具体实现方案开源的也有很多,原理也大同小异,本篇文章以Nuwa为例,深入剖析. Nuwa的github地址 https://github.com/jasonross/Nuwa 以及用于 ...
随机推荐
- Ext 初级UI设计
Ext.Button 说明:该组件代替了传统submit,reset,buuton HTML控件构造参数: text: 按钮上的名称 handler:指定一个函数句柄,在默认事件触发时调用,此时的默认 ...
- linux 终端快捷键
1. 移动光标快捷键 ctrl+f 向前移动一个字符 ctrl+b 向后移动一个字符 alt+f 向前移动一个单词 alt+b 向后移动一个单词 ctrl+a 移动到当前行首 ctrl+e 移动到当前 ...
- apache 工作模式
apache三种工作模式: prefork(2.4前默认)/worker/event(2.4默认)内容整理来自以下网站http://m.blog.csdn.net/article/details?id ...
- rar压缩文件下载
//string fileName = "ceshi.rar";//客户端保存的文件名 //string filePath = Server.MapPath(&qu ...
- MySQL基础学习之视图
创建新的视图 CREATE VIEW 视图名 AS SELECT 属性,属性1,属性2 FROM 表名 创建新的视图并指定数据名 CREATE VIEW 视图名(新属性,新属性1,新属性2) ...
- jquery全选,jquery全不选,jquery反选
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- C# double float int string 与 byte数组 相互转化
在做通信编程的时候,数据发送多采用串行发送方法,实际处理的时候多是以字节为单位进行处理的.在C/C++中 多字节变量与Byte进行转化时候比较方便 采用UNION即可废话少说看示例:typedef u ...
- 工作流(Workflow)学习---基础知识整理
工作流定义: 工作流是将一组任务组织起来以完成某个经营过程:定义了任务的触发顺序和触发条件,每个任务可以由一个或多个软件系统完成,也可以由一个或一组人完成,还可以由一个或多个人与软件系统协作完成. 工 ...
- SSH搭建完美CURD,含分页算法
今日开始研究使用java平台上的框架解决web服务端的开发. 这是一个完整的SSH实例,在马士兵老师的SSH整合代码基础上,增加用户的增删改查,同时实现structs方式的分页 放出源代码供大家学习参 ...
- oracle常用SQL语句(汇总版)
Oracle数据库常用sql语句 ORACLE 常用的SQL语法和数据对象一.数据控制语句 (DML) 部分 1.INSERT (往数据表里插入记录的语句) INSERT INTO 表名(字段名1, ...