说了那么多的原理,也该手写撸撸代码去实现一下。
1、生成Dex文件
将class文件打包成dex文件
1.1 dx指令程序
要将class文件打包成dex文件,就需要用到dx指令,这个dx指令类似于java指令。我们知道,java的指令有javac、jar等等,之所以可以使用这类指令,是因为我们有安装过jdk,jdk为我们提供了java指令,相同的,dx指令也需要有程序来提供,它就在Android SDK的build-tools目录下各个Android版本目录之中。
1.2 dx指令的使用
dx指令的使用跟java指令的使用条件一样,有2种选择:
配置环境变量(添加到classpath),然后命令行窗口(终端)可以在任意位置使用。
不配环境变量,直接在build-tools/安卓版本 目录下使用命令行窗口(终端)使用。
第一种方式参考java环境变量配置即可,这里我选用第二种方式。下面我们需要用到的命令是:
dx --dex --output=dex文件完整路径 (空格) 要打包的完整class文件所在目录,如:
dx --dex --output=C:\Users\Administrator\Desktop\dex\classes2.dex C:\Users\Administrator\Desktop\dex
2、下载Dex文件
这个需要跟后台老大哥去交流
3、加载Dex
3.1 获取到当前应用的PathClassloader;
3.2 反射获取到DexPathList属性对象pathList;
3.3 反射修改pathList的dexElements
把补丁包patch.dex转化为Element[] (patch)
获得pathList的dexElements属性(old)
patch+old合并,并反射赋值给pathList的dexElements
public class Hotfix {
/**
* 1、获取到当前应用的PathClassloader;
* <p>
* 2、反射获取到DexPathList属性对象pathList;
* <p>
* 3、反射修改pathList的dexElements
* 3.1、把补丁包patch.dex转化为Element[] (patch)
* 3.2、获得pathList的dexElements属性(old)
* 3.3、patch+old合并,并反射赋值给pathList的dexElements
*/
public static void installPatch(Application application, File patch) {
if (!patch.exists()) {
return;
}
// 获取到当前应用的PathClassloader;
ClassLoader classLoader = application.getClassLoader();
try {
/**
* 反射获取到PathClassLoader父类BaseDexClassLoader中
* private final DexPathList pathList;
* 属性对象pathList;
*/
// 反射获得BaseDexClassLoader中的pathList成员变量
Field pathListFiled = SharedReflectUtils.findFiled(classLoader, "pathList");
// 设为可访问
pathListFiled.setAccessible(true);
// 获得PathClassLoader中的pathList对象
Object pathList = pathListFiled.get(classLoader);
/**
* 执行makeDexElements方法,解析我们的补丁包获得dexElements数组
* 把补丁包patch.dex转化为Element[] (patch)
*/
// 反射获得pathList中的makeDexElements方法
Method makePathElements = SharedReflectUtils.findMethod(pathList,
"makePathElements",
List.class, File.class, List.class);
// 设为可访问
makePathElements.setAccessible(true);
List<Object> patchs = new ArrayList<>();
patchs.add(patch);
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// 执行makeDexElements方法,解析我们的补丁包获得dexElements数组
Object[] patchElements = (Object[]) makePathElements.invoke(null, patchs, application.getCacheDir(), suppressedExceptions);
/**
* 获得pathList的dexElements属性(old)
*/
Field dexElementsFiled = SharedReflectUtils.findFiled(pathList, "dexElements");
Object[] dexElements = (Object[]) dexElementsFiled.get(pathList);
/**
* 3.3、patch+old合并,并反射赋值给pathList的dexElements
*/
// 创建新的Element数组
Object[] newElements = (Object[]) Array.newInstance(patchElements.getClass().getComponentType(),
patchElements.length + dexElements.length);
System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);
System.arraycopy(dexElements, 0, newElements, patchElements.length, dexElements.length);
dexElementsFiled.set(pathList,newElements);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class SharedReflectUtils {
public static Field findFiled(Object object, String name) throws NoSuchFieldException {
Class<?> cls = object.getClass(); //PathClassLoader.class
while (cls != Object.class) {
try {
Field field = cls.getDeclaredField(name);
if (field != null) {
// 设置访问权限
field.setAccessible(true);
return field;
}
} catch (NoSuchFieldException e) {
}
cls = cls.getSuperclass();//BaseDexClassLoader.class
}
throw new NoSuchFieldException(object.getClass().getSimpleName() + " not find " + name);
}
public static Method findMethod(Object object, String name, Class<?>... parameterTypes) throws NoSuchMethodException {
Class<?> cls = object.getClass();
while (cls != Object.class) {
try {
Method method = cls.getDeclaredMethod(name, parameterTypes);
if (method != null) {
// 设置访问权限
method.setAccessible(true);
return method;
}
} catch (NoSuchMethodException e) {
}
cls = cls.getSuperclass();
}
throw new NoSuchMethodException(object.getClass().getSimpleName() + " not find " + name);
}
}