首先apk就是一个压缩文件,解压apk文件的内容如下图:
安卓原生热修复主要原理图和流程图如下,我花了好长时间才绘好,中间改了好几次,应该来说是很直观明白的,其中有截取了BaseDexClassLoader的关键源码,还有DexPathList的源码
a.现将打包好的dex文件传入手机中。
b.开始撸代码(主界面)
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_fix:
startHotFix();
break;
case R.id.btn_test:
testInject();
break;
}
}
//开始热修复
private void startHotFix() {
//pathClassLoader的特点:只能加载本工程目录下的dex文件。下面是源码的注释
//operates on a list of files and directories in the local file system, but does not attempt to
//load classes from the network
//创建SD卡目录存放热修复的dex
File srcDexFolder = initSrcDexFile();
//从SD卡将下载好的dex复制到存放目录
//创建要在工程目录存放dex的目录
File destFile = initDestDexFile();
//开始将刚下载的dex包复制到工程目录下载面
copyDex2DestFolder(srcDexFolder, destFile);
//开始合并dex
try {
assembleDex(destFile);
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG, "startHotFix: =异常=" + e.toString());
}
}
//初始化搬迁目的dex目录
private File initDestDexFile(){
File file = getDir(folderName, MODE_PRIVATE);
//复制之前,判断之前热修复文件是否存在
File destFile = new File(file.getAbsolutePath());
if (!destFile.exists()) {
destFile.mkdirs();
} else {
//清空残留的dex
File[] list = destFile.listFiles();
if (list != null && list.length > 0) {
for (File fileContent : list) {
Log.i(TAG, "startHotFix:要是删除的:== " + fileContent.getAbsolutePath());
fileContent.delete();
}
}
}
Log.i(TAG, "startHotFix: =path2=" + destFile.getAbsolutePath());
return destFile;
}
//预备热修复的源目录
private File initSrcDexFile() {
String downLoadedDex = getExternalCacheDir().getAbsolutePath()
+ File.separatorChar + folderName;
File srcDexFolder = new File(downLoadedDex);
if (!srcDexFolder.exists()) {
srcDexFolder.mkdirs();
} else {
//测试里面有几个刚下载的dex包
File[] files = srcDexFolder.listFiles();
if (files != null && files.length > 0) {
for (File file : files) {
Log.i(TAG, "startHotFix: =dexPath=" + file.getAbsolutePath());
}
}
}
Log.i(TAG, "startHotFix: =path1=" + srcDexFolder.getAbsolutePath());
return srcDexFolder;
}
//加载新dex,插入原有dex之前
private void assembleDex(File destFile) throws Exception {
//利用应用本身pathClassLoader加载应用的dex
/**
* his method should be overridden by class loader implementations that
* follow the delegation model for loading classes, and will be invoked by
* 父类委托,所以要强转
* pathClassLoader extends BaseDexClassLoader
*/
PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader();
File dexOutputDir = getCodeCacheDir();//8.0源码推荐
File[] files = destFile.listFiles();
//获取app的libs路径
String librarySearchPath = getFileStreamPath("libs").getAbsolutePath();
if (files != null && files.length > 0) {
for (File file : files) {
//一 加载new dex
DexClassLoader dexClassLoader = new DexClassLoader(
file.getAbsolutePath(), //String dexPath,
dexOutputDir.getAbsolutePath(),//String optimizedDirectory,
null, //String librarySearchPath,
getClassLoader() //ClassLoader parent
);
//1.获取new dex加载中 Pahtlist
Object newpathList = reflectPathList(dexClassLoader, "pathList");
//2.获取 new dex 中的dexElements
Object newDexElements = reflectDexElements(newpathList, newpathList);
//二 加载old dex
//1.获取 old dex中的PathList
Object oldPathList = reflectPathList(pathClassLoader, "pathList");
//2.获取 old dex中的dexElements
Object oldDexElements = reflectDexElements(oldPathList, oldPathList);
//三 将新的dex 插入到旧dexElements中去
Object fixedElement = injectElements(newDexElements, oldDexElements);
//四 重新设置pathList的Element[] dexElements
setFixedElements(reflectPathList(pathClassLoader, "pathList"), fixedElement);
}
} else {
Log.i(TAG, "assembleDex: =为空=");
}
}
//反射得到classLoader中的成员DexPathList pathList
private Object reflectPathList(Object object, String field) throws Exception {
//错误的反射
/*BaseDexClassLoader classLoader = (BaseDexClassLoader) getClassLoader();
Class<? extends BaseDexClassLoader> aClass = classLoader.getClass();
//pathList在pathClassLoader中为空,在父类BaseDexClassLoader
Field dexPathList = aClass.getDeclaredField("pathList");
dexPathList.setAccessible(true);
Log.i(TAG, "assembleDex: =name="+dexPathList.getName());*/
//报错原因如下:(被藏起来了,只反射到子类)
//java.lang.NoSuchFieldException: No field pathList in class dalvik/system/PathClassLoader;
//正确的反射
Class<?> baseClazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathList = baseClazz.getDeclaredField(field);
pathList.setAccessible(true);
Log.i(TAG, "assembleDex: =反射正常:=" + pathList.getName());
return pathList.get(object);
}
//反射得到pathList中的成员Element[] dexElements
private Object reflectDexElements(Object belongWho, Object pathList) throws Exception {
Class<?> aClass = pathList.getClass();
Field dexElements = aClass.getDeclaredField("dexElements");
dexElements.setAccessible(true);
return dexElements.get(belongWho);
}
//两个文件夹复制
private void copyDex2DestFolder(@NonNull File sreFolder, @NonNull File destFolder) {
dexList.clear();
File[] filesList = sreFolder.listFiles();
if (filesList != null && filesList.length > 0) {
for (File file : filesList) {
//开始复制
if (file.getName().endsWith(".dex")) {
copyFile(file, destFolder);
}
}
//复制完成之后,测试是否复制成功
testCopyResult(destFolder);
} else {
Log.i(TAG, "copyDex2DestFolder: =复制内容为空=");
}
}
//单个文件复制,一顿流操作
private void copyFile(@NonNull File file, @NonNull File destFolder) {
String name = file.getName();
Log.i(TAG, "copyFile: =name=" + name);
String destPath = destFolder.getAbsolutePath() + File.separator + name;
File destFile = new File(destPath);
try {
FileOutputStream outs = new FileOutputStream(destFile);
FileInputStream ins = new FileInputStream(file);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = ins.read(buffer)) != -1) {
outs.write(buffer, 0, len);
}
outs.close();
ins.close();
} catch (Exception e) {
e.printStackTrace();
}
}
核心代码
//替换掉pathList原有的数组
private void setFixedElements(Object pathList, Object fixedElement) throws Exception {
Class<?> aClass = pathList.getClass();
Field dexElements = aClass.getDeclaredField("dexElements");
dexElements.setAccessible(true);
dexElements.set(pathList, fixedElement);
}
//插入原有数组中去
private Object injectElements(@NonNull Object newDexElements, @NonNull Object oldDexElements) {
//获取数组中存放类型
Class<?> componentType = newDexElements.getClass().getComponentType();
if (componentType != null) {
int lengthNew = Array.getLength(newDexElements);
int lengthOld = Array.getLength(oldDexElements);
int totalLength = lengthNew + lengthOld;
Log.i(TAG, "injectElements: =数组长度=lengthNew" + lengthNew + "||lengthOld" + lengthOld);
Object fixedElement = Array.newInstance(componentType, totalLength);
for (int i = 0; i < totalLength; ++i) {
if (i < lengthNew) {
Object element = Array.get(newDexElements, i);
Array.set(fixedElement, i, element);
} else {
Object element = Array.get(oldDexElements, i - lengthNew);
Array.set(fixedElement, i, element);
}
}
return fixedElement;
}
return null;
}
点击热修复按钮日志
点击测试插入是否成功日志
总结:1.查看安卓源码,理顺classLoader类的关系,后面就一目了然,最有价值的还是第二张图,上面都有原理和步骤
2.源码注释很经典,都说的很清楚,甚至有的上面都有介绍了最新用法。