Android|Android ClassLoader

本文参考《Android进阶解密》作者刘望舒,吃水不忘挖井人。 按照惯例先给大家来个段子。 Android|Android ClassLoader
文章图片
哈哈哈嗝
正文开始

目录
1,javaClassLoader
2,AndroidClassLoader
3,知识点总结
Android|Android ClassLoader
文章图片
image.png 1,Java中的ClassLoader
简介
类名 编写语言 加载目录
bootstrapCLassloader cpp JAVA_Home\jre\lib
ExtensionsClassloader java java_home\jre\lib\ext
applicationClassLoader java classpach 路径下
双亲委派机制
双亲委派机制的优点
1,避免重复加载相同的类,也就是一个类,无论从自己到父类只能有一个Loader进行加载,并且缓存
2,保证核心类的安全性,例如,自定义String想要去替换系统核心的类。
代码
protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ // First, check if the class has already been loaded Class c = findLoadedClass(name); //步骤1 if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); //步骤2 } else { c = findBootstrapClassOrNull(name); //步骤3 } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } //步骤4 if (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } return c; // 步骤5 }

理论 当需要加载一个类的时候
1,首先判断是否加载过,如果有直接返回。
2,如果没有加载过,先让自己的父类去加载,父类再调用自己的父类加载,一直到父类为null的时候。
3,调用native方法进行加载也就是bootStrapClassloader进行加载。
4,如果父类能加载直接返回,如果没有加载到就自己去加载
5,返回加载的类。
2,Android中的ClassLoader
Android中的classLoader 和java 有所不同,我们都知道Java加载的是jar包文件,Android 加载的是dex文件
CLassLoader
名称 继承 作用
BootClassLoader classLoader Android中所有ClassLoader的parent
BaseDexClassLoader classLoader 加载dex文件,主要逻辑都在这里边
SecureClassLoader classLoader 增加安全性
URLClassLoader SecureClassLoader URL路径从jar文件中加载类和资源
PathClassLoader BaseDexClassLoader Android默认加载器,只能加载已经安装的apk文件/data/app目录)
DexClassLoader BaseDexClassLoader
InMemoryDexClassLoader BaseDexClassLoader 加载内存中的类文件
BaseDexClassLoader
构造函数
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted) { super(parent); this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); //1 ... }

【Android|Android ClassLoader】dexPath:包含类和资源的 apk文件列表,由 File.pathSeparator分隔,在Android上默认为:,(默认为冒号)
optimizedDirectory:由于dex文件被包含在APK,因此在装载目标类之前需要先从APK文件中解压出dex文件,该参数就是制定解压出的dex 文件存放的路径。这也是对apk中dex根据平台进行ODEX优化的过程。自API26开始无效。
librarySearchPath:指目标类中所使用的C/C++库存放的路径,可以为null。
parent:父ClassLoader引用。
看一下BaseDexClassLoader 的findClass方法
protected Class findClass(String name) throws ClassNotFoundException { List suppressedExceptions = new ArrayList(); //这里是重点 Class c = pathList.findClass(name, suppressedExceptions); //1 if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; //2 } return c; }

注意这里的pathList,在构造函数中创建,这是一个对象并不是一个集合,1处实际调用了pathList对象的findClass方法,第一个参数是全类名,第二个参数是异常集合,把方法内部抛出的异常,添加进去,这种类似C++的指针传了进去,在2处的代码抛出。
DexPathList 源码
final class DexPathList {private final Element[] dexElements; //数组,这里其实就是把dex文件封装起来了public Class findClass(String name, List suppressed) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { //核心 调用DexFile 元素的loadClassBinaryName方法去加载 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; } }

DexPathList 的findClass 方法会遍历当前数组,每一个对象Element拿到它封装的DexFile 对象调用 loadClassBinaryName去加载类。
Element 源码
/*package*/ static class Element { private final File dir; private final boolean isDirectory; private final File zip; private final DexFile dexFile; private ZipFile zipFile; private boolean initialized;

DexFile 源码
public final class DexFile {//方法1 public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) { return defineClass(name, loader, mCookie, suppressed); } //方法2 private static Class defineClass(String name, ClassLoader loader, Object cookie, List suppressed) { Class result = null; try { //核心,最后调用native去进行加载 result = defineClassNative(name, loader, cookie); } catch (NoClassDefFoundError e) { if (suppressed != null) { suppressed.add(e); } } catch (ClassNotFoundException e) { if (suppressed != null) { suppressed.add(e); } } return result; }}

知识点总结
1,双亲委派机制原理
2,java 和Android classLoader的不同
3,Android 加载类的流程
BaseDexClassLoader 持有一个pathList 对象,pathList对象内部持有一个Element对象数组,每个Element对象封装了一个DexFile对象
DexFile对象调用loadClassBinaryName 方法,最后调用native去进行加载。
为什么要介绍BaseDexClassLoader ,因为Android 加载dex文件的两个classloader PathClassLoader DexClassLoader都是他的子类,而且没有什么拓展只是控制了传入参数的不同。
PathClassLoader
public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super(dexPath, null, libraryPath, parent); } }

所有的构造函数,调用super的时候第二个参数都传入了null,这个参数是dex解压目录,所以我们不能操作PathClassLoader 加载的目录,这是Android 默认加载的类。
DexClassLoader
public class DexClassLoader extends BaseDexClassLoader { /** * Creates a {@code DexClassLoader} that finds interpreted and native * code.Interpreted classes are found in a set of DEX files contained * in Jar or APK files. * * The path lists are separated using the character specified by the * {@code path.separator} system property, which defaults to {@code :}. * * @param dexPath the list of jar/apk files containing classes and *resources, delimited by {@code File.pathSeparator}, which *defaults to {@code ":"} on Android * @param optimizedDirectory directory where optimized dex files *should be written; must not be {@code null} * @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 DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); } }

构造函数,第二个参数我们可以自己定义,所以我们可以操作DexClassLoader 加载的目录,这就是其中一种热修复的方案核心所在。
最后附上源码地址,Android源码
Android|Android ClassLoader
文章图片

    推荐阅读