替换dex实现热修复
来源:互联网 发布:幂等矩阵的秩和迹 编辑:程序博客网 时间:2024/06/05 20:22
安卓App热补丁动态修复技术介绍
本文就是对上面的原理进行简单实现。
主要思想:
首先一个确保apk是由多个dex组成的,dex1、dex2、dex3等。
dex1一般会包含application等。
假如dex2中出现了bug,那么我们可以修复相应的bug,生成对应的newdex2,然后将newdex2放置到dexements数组的前面。
那么其他方法调用dex2中的方法时,会先从数组的由前往后遍历,如果在newdex2中找到了对应的方法,就不再向后读取dex2.
所以实现的关键在于:
1.apk多分包
2.修复bug后,生成patch.dex
3.将patch.dex插入到dex2前面
apk多分包
上面124步骤,即可实现多dex分包。
那么3是什么意思呢?
首先dex.keep
com.study.dexhotfix/MainActivity.classcom.study.dexhotfix/MyApplication.class
multiDexKeepFile file(‘dex.keep’)的作用就是将dex.keep定义的类放到第一个dex中
生成patch.dex
先看一下新建项目后的目录结构
TestBug.java
修复前
public class TestBug { public int calculate(){ int i = 0; int j = 10; return j/i; }}
修复后
public class TestBug { public int calculate(){ int i = 1; int j = 10; return j/i; }}
修复后,Build->Rebuild Project
然后在/Users/likuan/Desktop/ST/DexHotFix/app/build/intermediates/classes/debug/com/study/dexhotfix/TestBug.class中找到TestBug.class
在桌面新建一个文件夹bug,然后将TestBug.class拷贝到bug/com/study/dexhotfix文件下
bug文件下的路径,一定要和项目中的路径一致
打开终端,找到/Users/xxx/Library/Android/sdk/build-tools/26.0.2目录(其他也可以)
然后执行如下命令:
./dx --dex --output=/Users/xxx/Desktop/bug/patch.dex /Users/xxx/Desktop/bug
./dx –dex –output={生成的dex目录} {要加载的class文件目录,目录下可以有多个class文件}
然后在bug目录下生成:patch.dex,这就是我们后面要用到的补丁dex
将patch.dex插入到bugdex前面
关键都在BugFixUtils.java
package com.study.dexhotfix;import android.content.Context;import android.os.Environment;import android.util.Log;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.lang.reflect.Array;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.List;import dalvik.system.DexClassLoader;import dalvik.system.PathClassLoader;/** * Created by ygdx_lk on 17/12/4. */public class BugFixUtils { public static final String DEX_DIR = "odex"; public static final String dexName = "patch.dex"; private static final String TAG = "BugFixUtils"; public static void fixbug(Context context){ String patchPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + dexName; downLoadPatch(context, patchPath); loadPatch(context); } //加载补丁dex public static void loadPatch(Context context) { List<File> dexs = getPatchDexs(context); if(dexs != null && dexs.size() > 0){ inject(context, dexs); } } //注入 private static void inject(Context context, List<File> dexs) { Log.e(TAG, "inject: "); //dex存储目录 File fileDir = context.getDir(DEX_DIR, Context.MODE_PRIVATE); //dex加载后的缓存目录 String optimizeDir = fileDir.getAbsolutePath() + File.separator + "opt_dex"; File optFile = new File(optimizeDir); if(!optFile.exists()){ optFile.mkdirs(); } //获取app的类加载器 PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader(); for (File dex : dexs) { Log.i(TAG, "inject: 获取dex。。。" + dex.getAbsolutePath()); //加载修复的dex文件 //public DexClassLoader( //String dexPath, dex路径 // String optimizedDirectory, dex加载的缓存路径 // String librarySearchPath, // ClassLoader parent) DexClassLoader dexClassLoader = new DexClassLoader(dex.getAbsolutePath(), optFile.getAbsolutePath(), null, pathClassLoader); try { //获取DexClassLoader和PathClassLoader中的DexElements Object dexObj = getPathList(dexClassLoader); Object pathObj = getPathList(pathClassLoader); Object dexElements = getDexElements(dexObj); Object pathElements = getDexElements(pathObj); //合并 Object mergeElements = combineArray(dexElements, pathElements); //将mergeElements覆盖pathClassLoader中的elements setField(pathObj,"dexElements", mergeElements); Log.e(TAG, "inject: merge"); }catch (Exception e){ Log.e(TAG, "inject: " + e.getMessage()); e.printStackTrace(); } } } //获取dalvik.system.BaseDexClassLoader中的DexPathList pathList private static Object getPathList(Object baseDexClassLoader) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList"); } private static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true); return localField.get(obj); } //获取DexPathList中的Element[] dexElements private static Object getDexElements(Object paramObject) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException { return getField(paramObject, paramObject.getClass(), "dexElements"); } private static void setField(Object obj, String field, Object value) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Class<?> cl = obj.getClass(); Field localField = cl.getDeclaredField(field); localField.setAccessible(true); localField.set(obj, value); } /** * * @param arrayLhs 放到前面的数组 * @param arrayRhs 放到后面的数组 * @return */ private static Object combineArray(Object arrayLhs, Object arrayRhs) { Class<?> localClass = arrayLhs.getClass().getComponentType(); int i = Array.getLength(arrayLhs); int j = i + Array.getLength(arrayRhs); Log.i(TAG, "combineArray: i" + i + " j" + (j - i)); Object result = Array.newInstance(localClass, j); for (int k = 0; k < j; ++k) { if (k < i) { Array.set(result, k, Array.get(arrayLhs, k)); } else { Array.set(result, k, Array.get(arrayRhs, k - i)); } } return result; } //获取DEX_DIR下的补丁dexs private static List<File> getPatchDexs(Context context) { List<File> patchDexs = new ArrayList<>(); File fileDir = context.getDir(DEX_DIR, Context.MODE_PRIVATE); if(fileDir != null && fileDir.exists()){ File[] files = fileDir.listFiles(); if(files != null){ for (File file : files) { if (file != null) { String fileName = file.getName(); //判断是否是补丁dex(patch.dex)可能有多个 if(fileName.startsWith("patch") && fileName.endsWith(".dex")){ patchDexs.add(file); } } } } } return patchDexs; } //从服务器或本地下载补丁dex private static void downLoadPatch(Context context, String patchPath) { // /data/data/packagename/odex File fileDir = context.getDir(DEX_DIR, Context.MODE_PRIVATE); String filePath = fileDir.getAbsolutePath() + File.separator + dexName; File file = new File(filePath); if(file.exists()){ file.delete(); } //拷贝补丁到/data/data/packagename/odex下 FileInputStream is = null; FileOutputStream os = null; try { is = new FileInputStream(patchPath); os = new FileOutputStream(file); int len = 0; byte[] buffer = new byte[1024]; while ((len = is.read(buffer)) != -1){ Log.e(TAG, "downLoadPatch: " + len); os.write(buffer, 0, len); } Log.e(TAG, "downLoadPatch: " + file.getAbsolutePath()); }catch (Exception e){ Log.e(TAG, "downLoadPatch: " + e.getMessage()); e.printStackTrace(); }finally { if(os != null){ try {os.close();} catch (IOException e) {e.printStackTrace();} os = null; } if(is != null){ try {is.close();} catch (IOException e) {e.printStackTrace();} is = null; } } }}
然后在MyApplication.java中添加loadPatch方法,这样每次启动应用,都会加载之前的补丁
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(base); new BugFixUtils().loadPatch(base); }
MainActivity.java
public class MainActivity extends AppCompatActivity { private TextView tv_result; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv_result = (TextView)findViewById(R.id.tv_result); } public void calculate(View view) { int result = new TestBug().calculate(); tv_result.setText("计算结果:" + result); } public void fixbug(View view) { BugFixUtils.fixbug(this); }}
测试
1.首先运行程序,点击计算执行calculate方法,报错。
2.然后,点击修复bug执行fixbug方法。
3.这时候点击calculate方法,依然会报错。
4.重启,点击计算calculate,发现计算结果为10,修复成功。
源码下载
- 替换dex实现热修复
- android dex热修复
- 一步步手动实现热修复(三)-Class文件的替换
- 一步步手动实现热修复(三)-Class文件的替换
- Dex多分包技术和热修复
- andfix热修复之生成dex文件
- 一步步手动实现热修复(一)-dex文件的生成与加载
- 一步步手动实现热修复(一)-dex文件的生成与加载
- 基于Dex分包方案---热修复、热更新、插件化
- Android热修复_待修复dex打包详细流程
- AndFix热修复实现
- Nuwa热修复实现
- Android热修复实现
- Nuwa热修复实现
- Android热修复实现
- Android dex分包方案以及热补丁修复
- Android热修复三部曲之动态加载补丁.dex文件
- Android热修复之dex多分包架构设计
- python模拟浏览器打开百度首页并登录或者点击首页新闻并保存网页
- hdu 5438 Ponds(并查集/拓扑排列)
- 问题二:win10+64 自动升级系统后,启动CentOS失败
- llvm Kaleidoscope tutorial 学习记录(1)
- 学龄1年的android小白2个月独立开发资深项目,还上了何俊林的推荐榜,他是怎么做到的(附项目源码)
- 替换dex实现热修复
- javascript实现异步上传图片
- Java 抽象类与接口
- Spring实现原理分析(二十六).Spring Boot关于嵌入式servlet容器
- Ali-Java编码规约插件,并检查编码规范
- [Python]《Python编程:从入门到实践》外星人入侵(一)
- C
- 异常断电导致PostgreSQL无法连接
- java线程