Android ClassLoader 学习笔记

基本类预加载

从linux系统启动到进入android世界之前, 简要流程就是

  • Bootloader 加载内核,初始化 CPU、内存,挂载文件系统
  • Linux 内核 启动,初始化设备驱动、挂载 rootfs,启动 init 进程
  • init 进程 解析 /init.rc,启动关键服务(如 ueventdvold),然后启动 Zygote
  • Zygote 进程 运行 ZygoteInit.main(),预加载 Java 类,并 fork() 启动 SystemServer

Zygote 是 Android 进程的孵化器,几乎所有的应用进程都是由它 fork 出来的。

zygote在native时做的工作(/system/bin/app_process)

(1)创建虚拟机–startVM
(2)注册JNI函数–startReg
(3)通过JNI获得Java层的com.android.internal.os.ZygoteInit 类,调用main 函数,进入java 世界

zygote在java时做的工作

(4)registerZygoteSocket()建立socket通道,zygote作为通信的服务端,用于响应客户端请求
(5)preload()预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率
(6)通过startSystemServer(),fork得力帮手system_server进程,也是Java Framework的运行载体(下面讲到system server再详细讲解)
(7)调用runSelectLoop(),随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作

preload加载基本类部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static void preload(TimingsTraceLog bootTimingsTraceLog) {
// ...省略
preloadClasses();
// ...省略
}

private static void preloadClasses() {
final VMRuntime runtime = VMRuntime.getRuntime();

// 读取 preloaded_classes 文件
InputStream is;
try {
is = new FileInputStream(PRELOADED_CLASSES);
} catch (FileNotFoundException e) {
Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
return;
}

// ...省略

try {
BufferedReader br =
new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);

int count = 0;
String line;
while ((line = br.readLine()) != null) {
// Skip comments and blank lines.
line = line.trim();
if (line.startsWith("#") || line.equals("")) {
continue;
}

Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
try {
// 逐行加载基本类
Class.forName(line, true, null);
count++;
// ...省略
} catch (Throwable t) {
// ...省略
}
}

// ...省略
} catch (IOException e) {
Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
} finally {
// ...省略
}
}

对于预加载, 在dalvik/art启动时将所有Java基本类和Android系统框架的基本类加载进来

这些类只需要在Zygote进程启动时加载一遍,后续APP或Android运行时环境的进程,都是从Zygote中fork出来,自然会继承zygote加载过的类

双亲委派

Android中的类加载器机制与JVM一样遵循双亲委派模式

所谓双亲委派就是指

  1. 加载.class时以递归的方式逐级向上委托给父加载器ParentClassLoader, 父加载器首先判断是否加载过该class
    1. 加载过则直接向下返回
    2. 未加载过则继续向上提交
  2. 直到提交到顶层父加载器, 如果顶层父加载器也没有加载过, 则尝试加载, 如果加载失败则逐级向下交还调用者加载

代码如下/libcore/ojluni/src/main/java/java/lang/ClassLoader.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}

findLoadedClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Returns the class with the given <a href="#name">binary name</a> if this
* loader has been recorded by the Java virtual machine as an initiating
* loader of a class with that <a href="#name">binary name</a>. Otherwise
* <tt>null</tt> is returned.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @return The <tt>Class</tt> object, or <tt>null</tt> if the class has
* not been loaded
*
* @since 1.1
*/
protected final Class<?> findLoadedClass(String name) {
ClassLoader loader;
if (this == BootClassLoader.getInstance())
loader = null;
else
loader = this;
return VMClassLoader.findLoadedClass(loader, name);
}

VMClassLoader.findLoadedClass(loader, name) 是一个 native 方法,直接与 JVM 交互,用于查询 name 这个类是否已经被 loader 加载过。如果已经加载,返回 Class<?> 对象

如果当前 ClassLoaderBootClassLoader,则将 loader 设为 null,表示请求 JVM 内部查找是否加载过该类, 否则在对应的loader内寻找


如果当前没有找到该类, 则提交给父加载器递归调用LoadClass, 如果顶层父加载器也没找到就调用findClass进行加载类

android类加载器

对于android主要关注三个类加载器

  • 系统类的加载器(BootClassLoader)
  • 当前应用的类加载器(PathClassLoader)
  • 动态加载一个外部 dex 文件(DexClassLoader)

BootClassLoader是ClassLoader的子类, PathClassLoader 和 DexClassLoader是BaseDexClassLoader的子类, 而BaseDexClassLoader又是ClassLoader的子类

三者的父加载器关系是DexClassLoaderPathClassLoaderBootClassLoader -> Null

而BootClassLoader是全局唯一的

PathClassLoader实例则是每个APP都拥有一个

demo

一个测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.example.classloaderdemo;

import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import dalvik.system.DexClassLoader;
import java.io.File;

public class MainActivity extends AppCompatActivity {
private static final String TAG = "ClassLoaderDemo";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 1. 获取当前应用的类加载器(PathClassLoader)
ClassLoader pathClassLoader = getClassLoader();
Log.d(TAG, "PathClassLoader: " + pathClassLoader);

// 2. 获取 BootClassLoader(引导类加载器)
ClassLoader bootClassLoader = pathClassLoader.getParent();
Log.d(TAG, "BootClassLoader: " + bootClassLoader);

// 3. 获取外部 Dex(动态加载)
String dexPath = "/sdcard/external.dex"; // 需要手动放置一个 dex 文件
File optimizedDir = getDir("dex", MODE_PRIVATE);
DexClassLoader dexClassLoader = new DexClassLoader(dexPath,
optimizedDir.getAbsolutePath(),
null,
pathClassLoader);
Log.d(TAG, "DexClassLoader: " + dexClassLoader);

// 4. 打印类加载器的层级关系
printClassLoaderHierarchy(dexClassLoader);
}

private void printClassLoaderHierarchy(ClassLoader classLoader) {
while (classLoader != null) {
Log.d(TAG, "ClassLoader: " + classLoader);
classLoader = classLoader.getParent();
}
Log.d(TAG, "Reached BootClassLoader (null)");
}
}

日志输出如下

1
2
3
4
5
6
7
8
PathClassLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/\~~qHxEOlv8MqCgwCgGZR5sdQ==/com.example.classloaderdemo-zI8ZQUdmTUh3v21YxUjwhg==/base.apk"],nativeLibraryDirectories=[/data/app/\~~qHxEOlv8MqCgwCgGZR5sdQ==/com.example.classloaderdemo-zI8ZQUdmTUh3v21YxUjwhg==/lib/arm64, /system/lib64, /system/system_ext/lib64, /system/product/lib64, /vendor/lib64]]]
BootClassLoader: java.lang.BootClassLoader@31eea01
ClassLoader referenced unknown path: /sdcard/external.dex
DexClassLoader: dalvik.system.DexClassLoader[DexPathList[[],nativeLibraryDirectories=[/system/lib64, /system/system_ext/lib64, /system/product/lib64, /vendor/lib64]]]
ClassLoader: dalvik.system.DexClassLoader[DexPathList[[],nativeLibraryDirectories=[/system/lib64, /system/system_ext/lib64, /system/product/lib64, /vendor/lib64]]]
ClassLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/\~~qHxEOlv8MqCgwCgGZR5sdQ==/com.example.classloaderdemo-zI8ZQUdmTUh3v21YxUjwhg==/base.apk"],nativeLibraryDirectories=[/data/app/~~qHxEOlv8MqCgwCgGZR5sdQ==/com.example.classloaderdemo-zI8ZQUdmTUh3v21YxUjwhg==/lib/arm64, /system/lib64, /system/system_ext/lib64, /system/product/lib64, /vendor/lib64]]]
ClassLoader: java.lang.BootClassLoader@31eea01
Reached BootClassLoader (null)

BootClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class BootClassLoader extends ClassLoader {
//.....
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null);
}
// .....
@Override
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);

if (clazz == null) {
clazz = findClass(className);
}

return clazz;
}

BootClassLoader重写了findClass和loadClass两个方法

当一个类不存在时会调用Class.classForName进行类的加载, 与加载基本类时的Class.forName()不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
ublic final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
// ...省略

public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName(className, true, ClassLoader.getClassLoader(caller));
}

public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
loader = BootClassLoader.getInstance();
}
Class<?> result;
try {
result = classForName(name, initialize, loader);
} catch (ClassNotFoundException e) {
Throwable cause = e.getCause();
if (cause instanceof LinkageError) {
throw (LinkageError) cause;
}
throw e;
}
return result;
}

// 本地方法
static native Class<?> classForName(String className, boolean shouldInitialize,
ClassLoader classLoader) throws ClassNotFoundException;

// ...省略
}

可以发现forName实际上调用的还是classForName

ZygoteInit.preloadClasses()中调用Class.forName(),实际是指定BootClassLoader为类加载器,且只需要在预加载的时候进行类初始化,只需要一次
总之,通过 Class.forName() 或者 Class.classForName() 可以且仅可以直接加载基本类,一旦基本类预加载后,对于应用程序而言,我们虽然不能直接访问BootClassLoader,但可以通过Class.forName/Class.classForName加载

class文件加载

类加载时机

1
2
3
4
5
6
7
8
9
1.隐式加载:
(1)创建类的实例,也就是new一个对象
(2)访问某个类或接口的静态变量,或者对该静态变量赋值
(3)调用类的静态方法
(4)反射Class.forName("android.app.ActivityThread")
(5)初始化一个类的子类(会首先初始化子类的父类)
2.显示加载:
(1)使用LoadClass()加载
(2)使用forName()加载

Class.forName 和 ClassLoader.loadClass加载有何不同:
(1)ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作)
(2)Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作

PathClassLoader

PathClassLoader 是作为应用程序的系统类加载器,也是在 Zygote 进程启动的时候初始化的

基本流程为:

1
2
3
4
ZygoteInit.main() -> 
ZygoteInit.forkSystemServer() ->
ZygoteInit.handleSystemServerProcess() ->
ZygoteInit.createPathClassLoader()

在预加载基本类之后执行,所以每一个 APP 进程从 Zygote 中 fork 出来之后都自动携带了一个 PathClassLoader,它通常用于加载 apk 里面的 .dex 文件

1
2
3
4
5
6
7
8
9
10
public class PathClassLoader extends BaseDexClassLoader {

public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}

public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}

DexClassLoader

可以从包含classes.dex的jar或者apk中,加载类的类加载器, 可用于执行动态加载, 但必须是app私有可写目录来缓存odex文件. 能够加载系统没有安装的apk或者jar文件, 因此很多热修复和插件化方案都是采用DexClassLoader

1
2
3
4
5
6
7
public class 
DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}

与PathClassLoader相比, DexClassLoader提供了optimizedDirectory,而PathClassLoader则没有

optimizedDirectory正是用来存放odex文件的地方,所以可以利用DexClassLoader实现动态加载

BaseDexClassLoader

BaseDexClassLoader是PathClassLoader和DexClassLoader共同的父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
//.....
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath, ClassLoader parent) {
this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
}

/**
* @hide
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath, ClassLoader parent, boolean isTrusted) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

if (reporter != null) {
reportClassLoaderChain();
}
}

@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, 额外增加了pathList字段, 并且重写了类加载函数findClass

1
2
3
4
dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用:分割;
optimizedDirectory: 优化后dex文件存在的目录, 可以为null;
libraryPath: native库所在路径列表;当有多个路径则采用:分割;
ClassLoader:父类的类加载器

并且其findClass其实是委托给了pathList的.findClass函数

PathList

该类主要用来查找Dex、SO库的路径,并这些路径整体呈一个数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final class DexPathList {
private Element[] dexElements;
private final List<File> nativeLibraryDirectories;
private final List<File> systemNativeLibraryDirectories;

final class DexPathList {
public DexPathList(ClassLoader definingContext, String dexPath,String libraryPath, File optimizedDirectory) {
...
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();

//记录所有的dexFile文件
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);

//app目录的native库
this.nativeLibraryDirectories = splitPaths(libraryPath, false);
//系统目录的native库
this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
//记录所有的Native动态库
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions);
...
}
}

DexPathList初始化过程,主要收集以下两个变量信息:
(1)dexElements: 根据多路径的分隔符;将dexPath转换成File列表,记录所有的dexFile
(2)nativeLibraryPathElements: 记录所有的Native动态库, 包括app目录的native库和系统目录的native库

makePathElements:

1
2
3
4
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}

makeDexElements:

makeDexElements方法的作用是获取一个包含dex文件的元素集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
Element[] elements = new Element[files.size()]; //获取文件个数
int elementsPos = 0;
for (File file : files) {
if (file.isDirectory()) {
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();
DexFile dex = null;
//匹配以.dex为后缀的文件
if (name.endsWith(DEX_SUFFIX)) {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] = new Element(dex, null);
}
} else {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
if (dex != null && isTrusted) {
dex.setTrusted();
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}

return elements;
}

该方法的主要功能是创建Element数组

loadDexFile:

加载DexFile文件,而且会把优化后的dex文件缓存到对应目录

1
2
3
4
5
6
7
8
9
10
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
Element[] elements)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements); //创建DexFile对象
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}

DexFile:

用来描述Dex文件,Dex的加载以及Class的查找都是由该类调用它的native方法完成的

1
2
3
4
5
6
7
8
9
10
DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
this(file.getPath(), loader, elements);
}

DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
mCookie = openDexFile(fileName, null, 0, loader, elements);
mInternalCookie = mCookie;
mFileName = fileName;
}

openDexFile:

1
2
3
4
5
6
7
8
private static Object openDexFile(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements) throws IOException {
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null) ? null : new File(outputName).getAbsolutePath(),
flags,
loader,
elements);
}
  1. sourceName为PathClassLoader构造函数传递的dexPath中以分隔符划分之后的文件名;
  2. outputName为null;
  3. flags = 0
  4. loader为null;
  5. elements为makeDexElements()过程生成的Element数组;

openDexFileNative:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static jobject DexFile_openDexFileNative(JNIEnv* env,
jclass,
jstring javaSourceName,
jstring javaOutputName ATTRIBUTE_UNUSED,
jint flags ATTRIBUTE_UNUSED,
jobject class_loader,
jobjectArray dex_elements) {
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == nullptr) {
return 0;
}
Runtime* const runtime = Runtime::Current();
ClassLinker* linker = runtime->GetClassLinker();
std::vector<std::unique_ptr<const DexFile>> dex_files;
std::vector<std::string> error_msgs;
const OatFile* oat_file = nullptr;

dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
class_loader,
dex_elements,
/*out*/ &oat_file,
/*out*/ &error_msgs);

if (!dex_files.empty()) {
jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
...
return array;
} else {
...
return nullptr;
}
}


整体加载流程如图