一、文件格式
1、class文件解析
一种8位字节的二进制流文件
1.2、class文件弊端
- 占用内存大,不适合移动端
- 堆栈的加栈模式,加载速度慢
- 文件IO操作多,类查找慢
2、dex文件解析
一种8位字节的二进制流文件,各个数据按顺序紧密的排列,无间隙,整个应用中所有java源文件都在一个dex
二、java 类加载
名称 | 加载目标 | 说明 |
Bootstrap ClassLoader(BootstrapClassLoader) | JAVA_HOME/jre/lib | 无法访问,是有c语言实现的 |
Extension ClassLoader(ExtClassLoader) | JAVA_HOME/jre/lib/ext | 上级为Bootstrap,通过代码获取为null |
Application ClassLoader(AppClassLoader) | classpath | 上级为Extension |
自定义加载类 | 自定义 | 上级为Applicaion1、/1、 |
三、Android加载器
1、类图
解析:
(1)BootClassLoader源码在ClassLoader文件里,但是不是内部类
(2)PathClassLoader和DexClassLoader都继承BaseDexClassLoader
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);
}
}
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
在8.0(API26)以前区别就是一个参数:optimizedDirectory,表示生成的odex(优化的dex)存放的路径。
//版本8.0之前
public class BaseDexClassLoader extends ClassLoader {
...
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
...
}
//8.0之后的版本
public class BaseDexClassLoader extends ClassLoader {
...
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
if (reporter != null) {
reporter.report(this.pathList.getDexPaths());
}
}
...
}
参数解析:
dexPath:dex或者文件jar的路径
optimizedDirectory:表示生成的odex(优化的dex)存放的路径
libraryPath:依赖so库的路径
四、双亲委托加载机制
1、图
代码如下:
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;
}
解析:
(1)loadClass方法是定义在ClassLoader类里面的,因为子类都没有覆写这个方法,双亲委托机制必须的条件
(2)每个ClassLoader内部都有一个成员ClassLoader parent,这个不是父类关系,类似上级classLoader,PathClassLoader的parent指向的是BootClassLoader,如果我们自定义DexClassLoader,那么在构造函数中传入的parent一般是PathClassLoader
(3)Framework层类用BootClassLoader加载,用户定义的类一般用PathClassLoader加载,包括谷歌集成的一些库,如AppCompatActivity也是由PathClassLoader加载的。
2、优点
(1)防止被重复加载,保证字节码唯一
(2)安全机制,防止核心类被篡改
3、自定义类加载器
- 继承ClassLoader父类
- 遵从双亲委派机制,重写findClass方法,不是重写loadClass方法
- 读取类文件的字节码
- 调用父类的defineClass方法来加载类
- 通过调用该类加载器的loadClass方法
5、BaseDexClassLoader解析
1、关键源码如下:
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;
}
......
}
成员变量pathList,它在构造函数中初始化。
2、DexPathList源码
final class DexPathList {
......
private final Element[] dexElements;
......
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
this.nativeLibraryDirectories = splitPaths(libraryPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null,
suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
......
/**
* 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()]);
}
}
DexPathList的成员变量数组dexElements,元素是Element类型,Element是其内部类,数组的赋值是在makePathElements方法(这个方法随着版本不一样)。
3、总结
3.1、谷歌的multiDex,腾讯的Tinker都是在操作dexElements这个变量。