本文就是对上面的原理进行简单实现。
主要思想:
首先一个确保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.class
com.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,修复成功。