个人总结笔记,不具备指导作用,怕说错了说漏了带坏其他同学,有错误的地方希望热心的同学可以指出,感谢
进入正片
首先记录一下在学习类加载机制过程中遇到的两个概念
1.ClassLoader(类加载)
其中包含BootClassLoader(负责加载Android FrameWork层的Class文件)、PathClassLoader(用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex,我们项目自己写的代码就是通过这个ClassLoader加载的,例如MainActivity,可以通过MainActivity.class.getClassLoader()获取到)、DexClassLoader(加载指定的dex,以及jar、zip、apk中的classes.dex)。
PathClassLoader与DexClassLoader之间没有任何区别,只不过DexClassLoader可以自定义odex的保存地址。
2.双亲委托机制
上面说到PathClassLoader负责加载我们自己写的Java文件的class文件,但是当我们查看PathClassLoader的时候发现PatchClassLoader中只有两个构造方法并没有加载class文件的函数,如下:
public class PathClassLoader extends BaseDexClassLoader {
/**
* Creates a {@code PathClassLoader} that operates on a given list of files
* and directories. This method is equivalent to calling
* {@link #PathClassLoader(String, String, ClassLoader)} with a
* {@code null} value for the second argument (see description there).
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
/**
* Creates a {@code PathClassLoader} that operates on two given
* lists of files and directories. The entries of the first list
* should be one of the following:
*
* <ul>
* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
* well as arbitrary resources.
* <li>Raw ".dex" files (not inside a zip file).
* </ul>
*
* The entries of the second list should be directories containing
* native library files.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
于是我们前往PathClassLoader的父类BaseDexClassLoader去寻找加载class的方法,依然没有找到加载class的方法,
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@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;
}
...
}
我们继续向上找到BaseDexClassLoader的父类ClassLoader,在这里我们看到了loadClass方法
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
loadClass的参数className就是我们需要查到的class,首先通过findLoadedClass方法在已经加载的class中寻找是否存在className,如果有则返回出去。否则将会调用parent.loadClass(className, false);去parent中调用loadClass方法,parent是什么呢?我们观察ClassLoader的构造方法,其中为parent赋值为ClassLoader中的静态内部类SystemClassLoader.loader。
/**
* Constructs a new instance of this class with the system class loader as
* its parent.
*/
protected ClassLoader() {
this(getSystemClassLoader(), false);
}
/**
* Returns the system class loader. This is the parent for new
* {@code ClassLoader} instances and is typically the class loader used to
* start the application.
*/
public static ClassLoader getSystemClassLoader() {
return SystemClassLoader.loader;
}
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
return new PathClassLoader(classPath, BootClassLoader.getInstance());
}
由此代码我们可以看出最终会创建一个以BootClassLoader为parent的PathClassLoader作为该ClassLoader的parent。
也就是说我们在使用ClassLoader.loadClass()的时候,ClassLoader会一直将类加载的任务交给自己的parent去做,然后一直向上传递,直到传到BootClassLoader。由此我们可以看出双亲委托机制命名的由来——自己不干,让爸爸干,爸爸不干,爷爷干。
使用双亲委托机制的好处是:
1.避免类的重复加载,如果parent加载过了就没有必要自己再加载了,直接拿来用就好了
2.安全,使用双亲委托机制就可以避免我们自定义一个与Java核心api中相同的类名的情况下取代Java核心api导致出现问题,例如我们自己写了一个String类来代替Java中的String类,因为使用了双亲委托机制,Java中的String类已经被加载了,就不会再加载我们自己写的类了。保证了Java核心功能不被替换。
上面讲完了loadClass的过程,然后我们发现如果我们需要的类在在parent中都找不到,这个时候就要靠我们自己了,就需要调用findClass方法。我们在逐级向上查找loadClass方法的过程中发现BaseDexClassLoader有重写ClassLoader的findClass方法,我们来看一下
@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;
}
Class c = pathList.findClass(name, suppressedExceptions);
这行代码就返回了我们需要的class否则就会直接报ClassNotFoundException。
其中pathList是一个DexPathList的对象,我们再前往DexPathList查看DexPathList.findClass
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
发现DexPathList.findClass又调用了DexFile.loadClassBinaryName方法去获取class。于是我们还要前往DexFile寻找DexFile.loadClassBinaryName
。。。路是真的远。
最终我们在DexFile中发现其调用defineClassNative(name, loader, cookie);得到class,这是一个native方法,其中的细节逻辑我们就无从得知了。搜寻之路到此结束。
回到DexPathList.findClass,我们发现他是通过遍历dexElements数组获取每个子项并获取子项中的dex文件,之后再从dex文件中获取到对应的class。dexElements是如何得到的呢?我们通过阅读代码发现在DexPathList的构造函数中对dexElements进行了初始化:
// save dexPath for BaseDexClassLoader
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
List<Element> elements = new ArrayList<>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
File dir = new File("");
DexFile dex = null;
String path = file.getPath();
String name = file.getName();
if (path.contains(zipSeparator)) {
String split[] = path.split(zipSeparator, 2);
zip = new File(split[0]);
dir = new File(split[1]);
} else if (file.isDirectory()) {
// We support directories for looking up resources and native libraries.
// Looking up resources in directories is useful for running libcore tests.
elements.add(new Element(file, true, null, null));
} else if (file.isFile()) {
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else {
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(dir, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
由于我们的class是从dex文件中读取出来的,因此进入
else if (file.isFile()) {
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
}
从这里获取到我们需要的dex并以Element(dir, false, zip, dex)的形式添加进dexElements。
到这里我们简单总结一下
总结
类在加载的过程中需要调用ClassLoader的loadClass进行加载,首先前往已经加载的内容中寻找,如果没找到则使用双亲委托机制parent.loadClass()进行加载,如果发现“家里人”都帮不上忙的时候,就需要自己来findClass。findClass的过程是通过加载dex文件进入dexElements数组并通过遍历找到对应名字的class,找到便返回出来。
由此我们可以确定热修复的思路,现有A.java,其中出现了待修复的bug,我们修复bug后生成了新的A.java,然后将其生成新的dex文件该dex中只需有我们修复后的A.java即可,并通过反射的手段将我们新生成的dex加入到dexElements的头部,这样当findClass再进行for循环的时候,它先找到我们新的dex文件中的A.java进行加载,这样就完成了新的文件替代旧的文件,实现了bug的热修复。
接下来的计划应该是会再总结一下反射的基本知识,主要目的还是为了自我巩固。一开始不想写博客的原因是怕自己写不出什么内容,感觉会和别人写的千篇一律,搞得网上乌烟瘴气的,这篇写的感觉倒是基本上就是我自己的学习成果,写得也比较顺(啰嗦),以后再写写应该会写得更加简洁易懂些吧,坚持吧。