本文为看雪论坛优秀文章
看雪论坛作者ID:镜中人24
关于虚拟机
1. 虚拟机是什么?
2. 虚拟机的工作流程
以Java虚拟机(JVM)为例,它的工作流程大致如下:
(3) 通过执行引擎与操作系统提供的接口交互。
3. Android的虚拟机
4. Android虚拟机与Java虚拟机的区别
以Davlik为例,与JVM主要存在三个区别:
(2) 应用体积更小,借助dx工具将class转成dex文件的过程中,会对代码进行一些优化,比如一些重复的方法等只会保留一份,所以体积会变小。
(3) 运行速度更快,在JVM中方法的调用主要是基于栈实现的,所以需要大量的入栈出栈,而Dalvik则是基于寄存器实现的,因此速度会更快,性能会有明显的提升。
关于类加载机制
(5) DexPathList:主要负责解析dex并以一个Element数组存储dex信息。
1. 完整的类加载机制
e. 从Element中取出DexFile,并调用其loadClassBinaryName完成类的加载。
2. 类加载机制的核心源码分析
(1) loacClass实现
首先我们看一下ClassLoader中的loadClass方法是如何实现的,如上图所示类的加载是基于双亲委托机制实现的,大致可以分为三步:
(a) 检查class是否被加载过。
(b) 判断parent是否为空,决定是调用BootClassLoader还是parent的loadClass方法。
(c) 如果前两步还没加载成功,则自己进行查找。
为什么要使用双亲委托机制呢?主要是考虑到了两方面的原因:
(3) DexPathList的findClass实现
我们的目的就是基于虚拟机的类加载机制,实现一个简单的热修复。看到这里相信大家都已经有思路了,既然是遍历数组,那么我们就可以通过在数组的第一个位置插入一个新的dex数据实现热修复。
热修复的简单实现
1. 准备工作
我们自己创建一个安卓应用,自己定义一个TestUtil类并实现一个test方法,抛出一个异常,关键代码如下:
(2) 用于修复异常的dex文件
首先,我们修改一下test方法,注释掉抛出异常的代码,然后build一下,通过javac将java文件编译成class文件。
2. 热修复的实现
b. 获取到类加载器的Class;
c. 反射获取DexPathList对象pathList;
d. 反射获取Element数组dexElements;
e. 获取补丁数组;
f. 合并两个数组;
g. 替换dexElements为合并之后的数组;
h. 调用安装补丁的方法。
package com.android.hotfix;
import android.app.Application;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class HotFix {
public static void installDex(Application myApp, String path){
//获取类加载器
ClassLoader loader = myApp.getClassLoader();
//获取到类加载器的Class
Class cls = loader.getClass();
//获取pathList对象
Field plField = null;
Object pathList = null;
try {
plField = cls.getSuperclass().getDeclaredField("pathList");
plField.setAccessible(true);
pathList = plField.get(loader);
} catch (Exception e) {
e.printStackTrace();
}
//获取Element数组
Object[] dexElements = null;
Field dexField = null;
if(pathList!=null){
try {
dexField = pathList.getClass().getDeclaredField("dexElements");
dexField.setAccessible(true);
dexElements = (Object[]) dexField.get(pathList);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取补丁数组
Object[] patchElements = makePatch(myApp,pathList,path);
if(patchElements!=null && dexElements !=null){
//合并两个数组
Object[] newElements = (Object[]) Array.newInstance(dexElements[0].getClass(),dexElements.length+patchElements.length);
System.arraycopy(patchElements,0,newElements,0,patchElements.length);
System.arraycopy(dexElements,0,newElements,patchElements.length,dexElements.length);
//替换合并后的数组
try {
dexField.set(pathList,newElements);
} catch (Exception e) {
e.printStackTrace();
}
try {
Object[] testField = (Object[]) dexField.get(pathList);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
private static Object[] makePatch(Application myApp, Object pathList, String path) {
Object[] rst = null;
try {
//获取makeElements方法
//Method makeMethod = pathList.getClass().getDeclaredMethod("makePathElements", java.util.List.class,java.io.File.class,java.util.List.class);
Method makeMethod = pathList.getClass().getDeclaredMethod("makeElements", java.util.List.class,java.io.File.class,java.util.List.class,boolean.class,ClassLoader.class);
//参数准备
List<File> dexFiles = new ArrayList<>();
File dexFile = new File(path);
dexFiles.add(dexFile);
File optimizedDirectory = myApp.getCacheDir();
List<IOException> suppressedExceptions = new ArrayList<>();
//调用方法
if(makeMethod!=null) {
makeMethod.setAccessible(true);
//return (Object[]) makeMethod.invoke(pathList,dexFiles,optimizedDirectory,suppressedExceptions);
return (Object[]) makeMethod.invoke(pathList,dexFiles,optimizedDirectory,suppressedExceptions,false,myApp.getClassLoader());
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
String dexPath = "/storage/emulated/0/Android/data/classes.dex";
File file = new File(dexPath);
if(file!=null && file.exists()) {
HotFix.installDex(this, dexPath);
}
}
存在的安全性问题
看雪ID:镜中人24
https://bbs.pediy.com/user-home-919715.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!
本文始发于微信公众号(看雪学院):基于虚拟机的类加载机制实现热修复
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论