是很久之前的技术了,之前也写过一篇类似的,有了一些新的理解看法,就写的详细一点。
学习这个之前,需要了解一下class文件到底是个啥。许多人可能写了这么多年java,但是根本就不知道.class文件是怎么组成的。大部分时候实现功能也用不上知道。所以很多时候你都只能浮于表面。
.Class文件
因为JVM是加载.class文件,使得JVM的可移植性很强,因为它只管加载.class文件就行了,无需管你是什么语言编译出来的.class。
.class文件是由无符号数与表组成:
无符号数:属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数。
表:表是由多个无符号数或者其他表作为数据项构成的复合数据类型,class文件中所有的表都以“_info”结尾。
那表是什么东西呢,首先要先知道.class文件的组成结构
魔数是一个4个字节的固定值,JVM用来校验你这个文件是不是.class文件。这里只着重说一下常量池。
常量池有18种TAG用来表示表的类型
我只拿出几个比较用的多的类型举例。尤其说下String类型这张表。它是由1个字节的TAG以及2个字节的地址下标组成。
会指向一个UTF8_info类型的表。UTF8_info由1个字节的TAG,2个字节长度的length以及一个bytes数组组成。bytes就是存放的char值,长度就是length。
常量池后面的类型也是由各种表组成,感兴趣的朋友可以自行查阅相关文档。
我们的android虚拟机是一个特殊的JVM,JVM是用来加载.class文件,基于栈结构,Dalvik是加载.dex文件,是寄存器结构。
.dex文件对.class文件进行了优化,去除一些冗余的字段,而且字节码上也有进行优化。
类加载器
BootClassLoader:Android系统启动时会使用BootClassLoader来预加载常用类。
PathClassLoader:8.0之后和DexClassLoader没啥区别。用来加载Dex与Apk文件。
重要的是类加载的机制。
双亲委派机制
1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
最简单的热修复方式就是借助这个机制。让被修复的类在后面加载,修复的类先被加载。5.0之后就不会有CLASS_ISPREVERIFIED标志了。所以只要你最小版本号高于5.0就无需去进行插桩,需要学习插桩看我另外一篇博客。
实现
public class HotFixUtil {
private static HotFixUtil hotFixUtil = new HotFixUtil();
private DexClassLoader dexClassLoader;
public static HotFixUtil getInstance() {
return hotFixUtil;
}
/**
* 加载修复的dex
* @param context
* @param name
*/
public void loadDex(Context context, String name) {
File fileDir = context.getDir("dex", Context.MODE_PRIVATE);
String dexPath = fileDir.getAbsoluteFile() + File.separator + name;
File dexFile = new File(dexPath);
if (!dexFile.exists()) {
try {
dexFile.createNewFile();
copyFiles(context, name, dexFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//获取dexElements
public void initDex(Context context) {
File fileDir=context.getDir("dex", Context.MODE_PRIVATE);
List<File> listDex = getList(fileDir);
try {
//获取pathList
Field pathListField = getField(context.getClassLoader(), "pathList");
Object pathList = pathListField.get(context.getClassLoader());
//获取老的dexElements
Field dexElementsField = getField(pathList, "dexElements");
Object[] oldDexElements = (Object[]) dexElementsField.get(pathList);
//获取 makeDexElements 方法
Method makeDexElements = getMethod(pathList, "makeDexElements", List.class, File.class,
List.class, ClassLoader.class);
//装载fix.dex
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
Object[] myDexElements = (Object[]) makeDexElements.invoke(pathList, listDex, fileDir, suppressedExceptions, context.getClassLoader());
//合并
Object[] newDexElements = (Object[]) Array.newInstance(oldDexElements.getClass().getComponentType(), oldDexElements.length + myDexElements.length);
System.arraycopy(myDexElements,0,newDexElements,0,myDexElements.length);
System.arraycopy(oldDexElements,0,newDexElements,myDexElements.length,oldDexElements.length);
//设置
dexElementsField.set(pathList,newDexElements);
Toast.makeText(context,"装载完成",Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
private List<File> getList(File fileDir){
File[] files = fileDir.listFiles();
List<File> list=new ArrayList<>();
for (File file : files) {
if(file.getName().endsWith(".dex")){
list.add(file);
}
}
return list;
}
public DexClassLoader getDexClassLoader() {
return dexClassLoader;
}
public static Field getField(Object object, String name) throws NoSuchFieldException {
Class<?> aClass = object.getClass();
while (aClass != null) {
try {
Field declaredField = aClass.getDeclaredField(name);
if (!declaredField.isAccessible()) {
declaredField.setAccessible(true);
}
return declaredField;
} catch (NoSuchFieldException e) {
e.printStackTrace();
aClass=aClass.getSuperclass();
}
}
throw new NoSuchFieldException("找不到字段");
}
public static Method getMethod(Object object, String name,Class... value) throws NoSuchMethodException {
Class<?> aClass = object.getClass();
while (aClass != null) {
try {
Method declaredMethod = aClass.getDeclaredMethod(name, value);
if (!declaredMethod.isAccessible()) {
declaredMethod.setAccessible(true);
}
return declaredMethod;
} catch (NoSuchMethodException e) {
e.printStackTrace();
aClass=aClass.getSuperclass();
}
}
throw new NoSuchMethodException("找不到方法");
}
public static void copyFiles(Context context, String fileName, File desFile) {
InputStream in = null;
OutputStream out = null;
try {
in = context.getAssets().open(fileName);
out = new FileOutputStream(desFile.getAbsolutePath());
byte[] bytes = new byte[1024];
int len = 0;
while ((len = in.read(bytes)) != -1)
out.write(bytes, 0, len);
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null)
in.close();
if (out != null)
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注释比较清晰了,代码也在git上,需要的自行下载吧~
代码地址:https://github.com/VjayYi/HotFixDemo
博客有借鉴一些其他博客内容,图片PPT均为自己所画。
.