Android中的ClassLoader:
BootClassLoader:
加载Android sdk中的类;
PathClassLoader:
加载已安装应用内的类;
DexClassLoader:
加载外部的类;
双亲委派机制:
loadclass("com.a.b.c")方法只有一个实现,在顶层ClassLoader中;
注意:父Parent并不是继承关系;如:PathClassLoader在双亲委派机制中的父Parent是BootClassLoader; DexClassLoader父Parent是PathClassLoader;
某个类加载器加载类时,首先从内存中找这个类,若没找到就将加载任务委派给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成加载任务或者没有父加载器时,才自己去加载,自己没有加载出来,就去存放所有dex的dexElements中寻找class。
dexElements就是我们需要hook到的变量,往数组最前面插新的dex;
public abstract class ClassLoader {
...
private final ClassLoader parent;
...
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 先从内存中找,
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {//若有父加载器,就让父加载器加载
c = parent.loadClass(name, false);
} else {//若没有父加载器,就自己加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
//从存放所有dex的集合dexElements中找class
c = findClass(name);
}
}
return c;
}
...
}
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
...
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
...
}
final class DexPathList {
...
//存放所有dex的集合
private Element[] dexElements;
...
//从存放所有dex的集合dexElements中找class
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
...
}
阿里sophix
方法执行过程:
- 调用对象.method()
- 从对象.method()获取方法的指令集地址
- 执行引擎根据指令集地址从class中拿到方法指令
- 将方法指令的栈帧放入栈中
- 进行执行栈帧得到结果
原理:c++替换方法区的class中有异常的arm指令地址;
优势:不需要重启app;
劣势:兼容性差,需要各个版本对应的头文件art_mothod.h;
一个java方法在ART虚拟机中对应一个ArtMethod结构体;
ArtMethod中有一个指针void * entry_point_from_quick_compiled_code_,指向了方法编译后的arm指令地址;
修改这个指针指向的有异常的地址,将地址改为正常的地址,就修改了要执行了arm指令;
腾讯Tinker
原理:class双亲委派机制,使用PathClassloader进行dex加载和插队;
优势:兼容性好;
劣势:需要重启app;
修复流程:
- 开发者将新的apk和老的有Bug的apk使用tinker的gradle插件进行差分打包(类似技术如bsdiff),生成体积很小的patch文件;
- 下载patch文件至app安装的私有目录;
- 后台开启新进程,将有bug的dex和patch进行合并,生成新的dex(里面是正常的无bug类);
- 拿到application的ClassLoader:PathClassLoader
- 使用PathClassLoader,通过反射从父类查找pathList对象
- 从pathList对象中反射得到dexElements
- 将新的dex放入数组dexElements最前面;
- 重启App,使用双亲委托机制,会先加载新的dex中的类,不再加载之后的dex中的bug类;实现修复bug;