这是一个简单的multidex实现热修复的demo
通过这个demo学习multidex和其热修复的原理及java反射知识
基础概念
- class
- dex
- dalvik
- arm
- .java
- .class
- .dex
dex文件是.class去冗余,合并相同代码区后的产物,一个dex是多个class的集合 dex文件中方法的索引从0到65535
方法数超限
dex unable to execute dex.method ID not in 65536
解决方案:多dex解决,使用multiDex
multiDex
使用方法
安卓版本 | 使用 |
---|---|
21以上 | 在defaultConfig里 使用multiDexEnable = true |
21以下 | 在defaultConfig里 使用multiDexEnable = true 引用multiDex支持库 然后配置Application 为MultiDexApplication的子类 或者在attachBaseContext中调用MultiDex.install |
MultiDex原理
编译期
.class 通过 dx命令 转为 classes.dex
multi-dex参数用于控制生成多个dex文件
MultiDex.install流程
- 判断虚拟机是否支持MultiDex
- 解压获取待安装的dex文件列表
- 把dex安装到classLoader中
运行期
虚拟机判断
虚拟机 | 版本 | 流程 |
---|---|---|
dalvik的jit | java.vm.version < 2.0.0 | apk install .dex 启动 jit(启动慢没有缓存,运行慢) 原生指令 运行 |
art的aot | java.vm.version >= 2.0.0 | apk install(aot) 原生指令 启动 执行 |
dex解压
apk中的dex安装包会解压到应用的内置目录data,解压是耗时任务
dex安装
.class 类加载 运行
multidex原理
- pathClassLoader中有一个pathList变量,其中是dexElement数组,类加载器会按照顺序加载dex文件
- MultiDex.install
- 反射获取到PathClassLoader的pathList
- 然后生成dex文件对应的element数组
- 将生成的element数组插入到原有的dexelements数组后面
热修复
原理
参考multidex,但是需要将修复包插入到数组的前面
生成代码补丁包
运行时注入代码补丁
通过javac 单独编译要编译的java文件 然后通过dx命令生成一个dex文件 然后插入pathList的element数组的第一个
补丁包生成流程
1、新建一个lib 拷贝原工程,然后删除除要修复的文件外的所有文件
2、javac java文件,生成.class文件
3、使用dx命令生成dex文件
.java javac .class dx classes.dex(多个)
反射工具类
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectUtil {
/**
* 获取某个属性
*
* @param instance
* @param name
* @return
*/
public static Field findField(Object instance, String name) throws NoSuchFieldException {
Class clazz = instance.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(name);
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field;
} catch (NoSuchFieldException e) {
e.printStackTrace();
clazz = clazz.getSuperclass();
}
}
throw new NoSuchFieldException("No Such Field :" + name);
}
public static Method findMethod(Object instance,
String name,
Class<?>... parameterTypes) throws NoSuchMethodException {
Class clazz = instance.getClass();
while (clazz != null) {
Method method = null;
try {
method = clazz.getDeclaredMethod(name);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
e.printStackTrace();
clazz = clazz.getSuperclass();
}
}
throw new NoSuchMethodException("No Such Method :" + name);
}
}
注入类
import android.content.Context;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
class HotFixManager {
public static void installFixDex(Context context) {
try {
File fixFile = new File("");
if (!fixFile.exists()) {
return;
}
Field pathListField = ReflectUtil.findField(context.getClassLoader(), "pathList");
Object dexPathList = pathListField.get(context.getClassLoader());
/**
* get method makeDexElements
*/
Method makeDexElements = ReflectUtil.findMethod(
dexPathList,
"makeDexElements",
List.class,
File.class,
List.class,
ClassLoader.class);
ArrayList<File> filesToBeInstall = new ArrayList<>();
filesToBeInstall.add(fixFile);
File operationDirectory = new File(context.getFilesDir(), "");
ArrayList<IOException> suppressedException = new ArrayList<>();
Object[] extraElements = (Object[]) makeDexElements.invoke(dexPathList,
filesToBeInstall, operationDirectory, suppressedException, context.getClassLoader());
/**
* 获取原始字段
*/
Field dexElements = ReflectUtil.findField(dexPathList, "dexElements");
Object[] originElements = (Object[]) dexElements.get(dexPathList);
Object[] combinedElements = (Object[]) Array.newInstance(
originElements.getClass().getComponentType(),
originElements.length + extraElements.length
);
/**
* insert
*/
System.arraycopy(extraElements, 0, combinedElements, 0, extraElements.length);
System.arraycopy(originElements, 0, combinedElements, extraElements.length, originElements.length);
/**
* swap
*/
dexElements.set(dexPathList, combinedElements);
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
multidex引起的ANR问题
multidex会先从apk中解压dex文件存放在应用的data目录下 接着会将dex注入到PathClassLoader中 然后通过dexopt优化为.odex文件 然后加载类的时候加载
如果解压或者dexopt的时间太长,就会引起主线程的卡顿引起anr
注:这个只会出现在应用安装后第一次启动过程
优化方案
不在主线程中进行操作 启动一个新的进程操作