Android 类加载机制

介绍 Android 类加载机制,双亲委派模型,BaseDexClassLoader, DexClassLoader, PathClassLoader 加载 dex, jar, apk 等文件。

JVM 虚拟机

执行方式

Java 的口号是“一次编译,到处运行”;也就是说 Java 程序的可执行文件(class 文件,字节码),是与机器硬件平台无关的;在程序运行时,由 JVM 将字节码翻译成当前运行环境处理器 CPU 的机器指令来执行。JVM 中通常有两种执行方式:

  • 解释执行
    字节码中每一行代码,由 JVM 解释器解释翻译成机器码再运行,依此循环从字节码中取出下一行解释并执行,因此解释执行的速度比较慢。
  • 编译执行
    JVM 将字节码编译成机器码后,运行机器码的执行方式,执行效率很高。

通常情况下,JVM 采用混合执行的方式来运行程序的:将高频代码编译成机器码编译执行,其他代码直接解释执行。

编译方式

编译执行的过程,有两种常见的编译方式:

  • JIT:Just In Time
    指在运行时编译,边运行边编译,属于动态编译。在程序启动时,将字节码编译成机器码,然后运行。优点是能更好的利用 Java 动态行为的特性(特别是多态,只有在运行时才能确定执行哪个方法);缺点是动态编译时间会计算在程序运行时间中,特别是会导致程序启动时间变长,以及程序运行期间编译带来的性能消耗。JIT 通常只会将执行频率高的热方法动态编译,获取更好的效率平衡。
  • AOT:Ahead Of Time
    指在运行前编译,编译完后再运行,属于静态编译。在程序运行前,将字节码编译成机器码存储到本地,程序启动时,直接启动对应的机器码。优点是不存在动态编译的内存和性能消耗,直接运行本地机器码启动速度块;缺点是 Java 动态特性带来的复杂性,影响了静态编译的代码质量。

编译方式对比:

动态 JIT 静态 AOT
平台无关性
代码质量 优秀 良好
利用动态行为
类和层次结构的知识
编译时间 有限制,有运行时成本 限制很少,无运行时成本
运行时性能影响
编译方式 需要谨慎编译,由 JIT 处理 需要谨慎编译,由开发人员处理

总的来说,动态编译 JIT 能提供最好的系统稳定性能;而静态编译 AOT 能提供最好的交互性能。

Android 虚拟机

概念

Android 虚拟机实现有两个阶段:

  • Dalvik
    Android K 4.4 之前的版本都是使用的 Dalvik 虚拟机。Java 生成的 class 文件由 dx 工具转换为 Dalvik 字节码即 dex 文件,之后进行签名对齐等操作变成 APK 文件。而 Dalvik 虚拟机负责将 dex 字节码解释为机器码,解释执行的速度慢效率低;从 Android 2.2 froyo 开始引入 JIT 动态编译执行方式,APP 运行时,JIT 将执行次数较多的 dex 文件动态编译为机器码缓存起来,后续再次执行时大大提高运行速度。JIT 动态编译的特点是:每次打开 APP 运行时,都需要重新编译。
  • ART: Android Runtime
    Android K 4.4 采用了 DalvikART 共存方式,两者可以相互切换;从 Android L 5.0 开始彻底丢弃 Dalvik 全面转换为 ART 方式。ART 虚拟机采用的是 AOT 静态编译执行方式,APP 在第一次安装时,dex 字节码会被预先编译成机器码;之后打开 APP 运行时,不需要额外的解释翻译工作,直接使用本地机器码执行,提高运行速度。

两者的区别

  • Dalvik 是运行时编译;ART 是运行前编译(安装时编译)
  • Dalvik 每次运行时都会将执行频繁的 dex 文件编译为机器码,运行启动时间加长,边运行边编译会额外销毁内存和系统性能
  • ART 在安装时编译,安装时间会延长;把程序代码转换成机器语言存储在本地,会消耗掉更多的存储空间,增幅通常不会超过应用代码包大小的 20%

文件格式

  • dex 文件格式
    魔数:dexAndroid 平台可执行文件,字节码;每个 APK 中包含一个或多个 dex 文件格式的可执行文件。
  • odex 文件格式
    魔数:deyodex: Optimize Dex 即优化后的 dex 文件。Dalvik 虚拟机从 APK 中将 dex 文件提取出来优化后生成 odex 文件,并存储在本地,后期运行时直接编译解释 odex 文件(仍然是字节码,dey 字节码)。而 APK 被提取后可以有也可以没有 dex 文件;在多 dex 文件的 APK 中,提取优化后只会生成一个 odex 文件。
  • oat 文件格式
    魔数:.elf ,是 ELF 格式的可执行文件。ART 虚拟机在 APK 安装时,将 dex 编译为本地机器码,并生成对应的 oat 文件格式的文件,可以直接执行。
  • vdex 文件格式
    魔数:vdex ,是 APKdex 文件的一份拷贝,同时会将多个 dex 合并为一个文件。在 Android O 8.0 之前,oat 格式文件除了机器码还包含一份 dex 的拷贝;但在 Android O 之后,dex2oat 静态编译时会产生两个文件:odex, vdex ,其中 odex 为机器码,通常很小;而 vdex 则是原始 dex 的一份拷贝,它是一个全新格式的文件(不是 ELF 格式)。
  • art 文件格式
    魔数:art ,是一个 img 文件,表示类对象映像;这个 img 文件直接被映射到 ART 虚拟机的堆空间中,包含了 oat 中的某些对象实例以及函数地址。

为保证 DalvikART 的兼容性,以及历史遗留问题,可能会使用相同文件后缀表示不同的文件格式,非常容易混淆。比如 .dex 后缀可以是 dex, oat 文件,odex 后缀可以是 odex, oat 文件等。

转换流程图

Java 源文件转换为 oat 格式文件的流程图:

0108-android-classloading-java2oat.png

文件分析工具

  • dex 文件生成及分析工具
    d8.bat/dx.bat :生成工具,位置路径为 Windows sdk\build-tools\28.0.3 。示例:d8.bat --output=test.jar Test.jar test.class ;其中 output 必须是 .zip, .jarinput 可以是 .dex, .apk, .jar, .class, .zip
    dexdump/dexdump2 :分析工具,在 AOSP 编译完后 out/host$ cd linux-x86/bin/ 目录下会生成对应工具。
  • oat 文件
    因为是 ELF 格式文件,所以通用工具都可以读出,如:readelfAOSP 编译完后生成的 otadump 也可以分析。
  • vdex 文件
    github: vdexExtractor ,这款工具可以从 vdex 中提取出原始的 dex 文件。

示例

如下信息手机系统为 Android O 8.1

  • 系统编译 framework 生成文件
    按照 out 目录生成文件路径,拷贝到手机对应路径中;在系统启动时,会拷贝一份到 /data/dalvik-cache/arm64 目录下。

    1
    2
    3
    4
    5
    6
    7
    8
    6.1M    system/framework/arm64/boot-framework.art
    25M system/framework/arm64/boot-framework.oat
    20M system/framework/arm64/boot-framework.vdex
    7.4M system/framework/framework.jar
    // 系统启动后在 /data/dalvik-cache/arm64 目录下生成对应文件
    6.0M system@framework@boot-framework.art
    0 system@framework@boot-framework.oat -> /system/framework/arm64/boot-framework.oat
    0 system@framework@boot-framework.vdex -> /system/framework/arm64/boot-framework.vdex
  • 系统编译应用 Email 生成文件
    系统应用在安装时同样会拷贝到 /data/dalvik-cache/arm64 目录下,但是会多生成一个 classes.art 文件。注意:在 data 目录生成的 dex 后缀的文件,实际上是 oat 文件pull 出来后魔数是 .elf 格式的。

    1
    2
    3
    4
    5
    6
    7
    6.7M    system/app/Email/Email.apk
    76K system/app/Email/oat/arm64/Email.odex
    4.1M system/app/Email/oat/arm64/Email.vdex
    // 系统启动后在 /data/dalvik-cache/ 目录下生成对应文件
    32K /data/dalvik-cache/arm64/system@app@Email@Email.apk@classes.art
    72K /data/dalvik-cache/arm64/system@app@Email@Email.apk@classes.dex
    4.0M /data/dalvik-cache/arm64/system@app@Email@Email.apk@classes.vdex
  • 安装 qsbk.apk 生成的文件,apk 中包含多个 dex 文件
    第三方应用在安装时,直接安装到 /data/app/ 目录下,并根据包名随机生成一个字符串做目录区分。注意:生成的 base.odex 实际上是 oat 文件pull 出来后魔数是 .elf 格式的。

    1
    2
    3
    32M     /data/app/qsbk.app-9P***/base.apk
    276K /data/app/qsbk.app-9P***/oat/arm/base.odex
    15M /data/app/qsbk.app-9P***/oat/arm/base.vdex
  • 小结
    系统自带 framework, app 都会生成 art, oat, vdex 三种文件格式的文件;而第三方应用安装后,只会生成 oat, vdex 两种文件格式的文件。其中 framework 中后缀为 .oat、系统 app 中后缀为 dex、第三方 app 中后缀为 odex这三个 oat, dex, odex 后缀的文件,实际上都是 oat 文件,注意别混淆了,仅仅是后缀不同而已。

代码速查表

本文基于 Android O 8.1 源码分析 Android 平台的 ClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
libcore/ojluni/src/main/java/java/lang/Class.java
libcore/ojluni/src/main/java/java/lang/ClassLoader.java
libcore/libart/src/main/java/java/lang/VMClassLoader.java

libcore/dalvik
./src/main/java/dalvik/system/BaseDexClassLoader.java
./src/main/java/dalvik/system/InMemoryDexClassLoader.java
./src/main/java/dalvik/system/DexClassLoader.java
./src/main/java/dalvik/system/PathClassLoader.java
./src/main/java/dalvik/system/DelegateLastClassLoader.java
./src/main/java/dalvik/system/DexPathList.java
./src/main/java/dalvik/system/DexFile.java

art/runtime/native/dalvik_system_DexFile.cc
art/runtime/native/java_lang_VMClassLoader.cc

Android 类加载器

类图结构

0108-android-classloading-classloader-class-diag.png

本文不分析 SecureClassLoader, URLClassLoader 这两个类加载器。

  • ClassLoader :抽象类,类加载器的最终父类
  • BootClassLoader :启动类加载器;在双亲委托模型中,是第一级父加载器
  • BaseDexClassLoaderAndroid 平台加载 dex 文件的基类
  • PathClassLoader :系统类加载器,也相当于应用类加载器,是用户自定义类加载器默认的父加载器;APK 文件都是该加载器加载的
  • DexClassLoader :主要用于从包含 classes.dex.jar, .apk 文件中加载类,而这些文件并没有随着应用一起安装
  • InMemoryDexClassLoader :主要用于加载内存中的 dex 文件,而这些内存中的文件并不需要存储在本地
  • DelegateLastClassLoader :最近委托查找策略类加载器,并不完全按照双亲委派模型来加载的,会提前执行 findClass 从加载 dex 文件中查找类,然后才是双亲委派模型

从各个博客看下来,ClassloaderAndroid 不同大版本中不管是代码位置还是子类都在不停的变化。

自定义类加载器时,如果不指定父加载器,则默认其父加载器为 PathClassLoader ;通过 Class 获取加载器时,如果其加载器为空,则指定其加载器为 BootClassLoader

双亲委派模型

0108-android-classloading-parent-delegation-model.png

类加载器的双亲委派模型:要求除了启动类加载器外,其余的类加载器都应当有自己的父加载器(父子不是继承关系,而是组合关系来复用父类代码)。双亲委派模型并不是强制要求,只是 Java 的推荐方式,可以通过重新加载方法来改变。
双亲委派模型原则:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。
Android 和标准 Java 对比:没有扩展类加载器,启动加载器 BootClassLoader 也是 Java 实现的。

父加载器和父类加载器是两个不同的概念:父加载器是参考双亲委派模型在构造方法中指定的;父类加载器表示当前加载器的父类(类图结构上是继承关系)。

ClassLoader

ClassLoader 是抽象类,其他所有的类加载都是它的继承类;有以下几个特点:

  • 标准 Java 中系统默认加载器为 AppClassLoader;而 Android 中系统类加载器是 PathClassLoader
  • 系统类加载器 PathClassLoader 的父加载器为启动加载器 BootClassLoader
  • Android 启动加载器 BootClassLoaderClassLoader 的内部类,也是其子类;默认为顶层父加载器
  • ClassLoader 构造方法中指定父加载器,如果不指定:父加载器默认为系统加载器 PathClassLoader ;即:自定义类加载器,如果不指定父加载器,则其父加载器默认为 PathClassLoader
  • loadClass 实现了双亲委派模型
  • findClass, defineClass 必须在子类中实现
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
public abstract class ClassLoader {

// 系统类加载器
static private class SystemClassLoader {
public static ClassLoader loader =
ClassLoader.createSystemClassLoader();
}

// 系统类加载器为 PathClassLoader
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath =
System.getProperty("java.library.path", "");

// String[] paths = classPath.split(":");
// URL[] urls = new URL[paths.length];
// for (int i = 0; i < paths.length; i++) {
// try {
// urls[i] = new URL("file://" + paths[i]);
// }
// catch (Exception ex) {
// ex.printStackTrace();
// }
// }
//
// return new java.net.URLClassLoader(urls, null);

// TODO Make this a java.net.URLClassLoader once we have those?
// PathClassLoader 的父加载器为 BootClassLoader
return new PathClassLoader(classPath, librarySearchPath,
BootClassLoader.getInstance());
}

// 系统加载器的类 PathClassLoader.class
protected final Class<?> findSystemClass(String name)
throws ClassNotFoundException {
return Class.forName(name, false, getSystemClassLoader());
}

// 系统类加载器为 PathClassLoader
public static ClassLoader getSystemClassLoader() {
return SystemClassLoader.loader;
}

// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;

public final ClassLoader getParent() {
return parent;
}

private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
}
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
// 如果不指定父加载器,则其父加载器默认为 PathClassLoader
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
public Class<?> loadClass(String name)
throws ClassNotFoundException {
return loadClass(name, false);
}

// 实现双亲委派模型
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
// 1. 先确认类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 2. 如果没有被加载,父加载器是否为空
// 不为空则父加载器加载,依次递归
c = parent.loadClass(name, false);
} else {
// 3. 这里沿用标准 Java 的双亲加载模型
// 但是 Android 中并没有 Bootstrap
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
// 4. 如果父加载器抛出 ClassNotFoundException
// 说明父加载器无法完成类加载请求
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
// 5. 父加载器无法完成加载或者其他原因没有加载成功
// 则调用本身的 findClass 进行类加载
c = findClass(name);
}
}
return c;
}

// 通过本地代码在 ART 中查找
protected final Class<?> findLoadedClass(String name) {
ClassLoader loader;
if (this == BootClassLoader.getInstance())
loader = null;
else
loader = this;
return VMClassLoader.findLoadedClass(loader, name);
}

// Android 中没有Bootstrap,直接返回 null
private Class<?> findBootstrapClassOrNull(String name) {
return null;
}

protected Class<?> findClass(String name)
throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

protected final Class<?> defineClass(...)
throws ClassFormatError {
throw new UnsupportedOperationException("...");
}
...
}

从源码 loadClass 中再描述下双亲委派模型:

  • 先确认类是否已经被加载
  • 如果没有被加载,且父加载器不为空;使用父加载器加载。依次递归
  • 父加载器为空,使用 Bootstrap 来加载,但是 Android 中并不存在该加载器,直接返回 null
  • 父加载器无法完成加载或者其他原因没有加载成功,则调用当前递归到的类加载器 findClass 进行类加载

BootClassLoader

启动类加载器 BootClassLoader,是 ClassLoader 的成员内部类,也是 ClassLoader 的子类;是顶层父加载器。和 JVM 不一样,Android 的启动类加载器是 Java 实现的。有如下特点:

  • 启动类加载器 BootClassLoader 的父加载器为 null ,它是最顶层的加载器
  • 因为是最顶层的父加载器,在 loadClass 中如果发现类没有加载,直接在这一层 findClass
  • findClass 是通过反射 Class.classForName (它是一个 native 方法,标准 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
26
27
28
29
30
31
32
33
34
35
36
37
38
class BootClassLoader extends ClassLoader {

private static BootClassLoader instance;

@...
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}

return instance;
}

// 启动类加载器的父加载器为 null
public BootClassLoader() {
super(null);
}

@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
// 通过反射加载类,classForName 是 native 方法
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;
}
...
}

Class

因为 Android 修改了标准 Java 的类加载器,所以在 Class.java 中也做了对应修改。主要有以下几个特点:

  • 默认类加载器和调用者为同一个加载器
  • 获取类加载器时,如果加载器为空,则指定其加载器为 BootClassLoader
  • native 方法 classForName ,从虚拟机中加载类
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
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
...
// defining class loader, or null for the "bootstrap" system loader.
private transient ClassLoader classLoader;

public static Class<?> forName(String className)
throws ClassNotFoundException {
return forName(className, true, VMStack.getCallingClassLoader());
}

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;
}

/** Called after security checks have been made. */
@FastNative
static native Class<?> classForName(String className,
boolean shouldInitialize, ClassLoader classLoader)
throws ClassNotFoundException;

public ClassLoader getClassLoader() {
if (isPrimitive()) {
return null;
}
// 如果其加载器为空,则指定其加载器为 BootClassLoader
return (classLoader == null) ?
BootClassLoader.getInstance() : classLoader;
}
...
}

PathClassLoader

Android 的系统类加载器,也相当于应用类加载器,是用户自定义类加载器默认的父加载器;类中只有构造方法,它继承了 BaseDexClassLoaderAPK 加载时,默认使用该加载器加载到 ART 中。

1
2
3
4
5
6
7
8
9
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

继承了 BaseDexClassLoader ,只有构造方法。主要用于从包含 classes.dex.jar, .apk 文件中加载类,而这些文件并没有随着应用一起安装;比如放在 assert 目录下等等。
而实际上,从构造方法传递的参数来看:DexClassLoader, PathClassLoader 并没有任何区别!!

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

InMemoryDexClassLoader

继承了 BaseDexClassLoader ,只有构造方法。主要用于加载内存中的 dex 文件,而这些内存中的文件并不需要存储在本地;方便网络下载并加载。

1
2
3
4
5
6
7
8
9
public final class InMemoryDexClassLoader extends BaseDexClassLoader {
public InMemoryDexClassLoader(ByteBuffer[] dexBuffers,
ClassLoader parent) {
super(dexBuffers, parent);
}
public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent){
this(new ByteBuffer[] { dexBuffer }, parent);
}
}

DelegateLastClassLoader

DelegateLastClassLoader 继承 PathClassLoader,是最近委托查找策略;它加载类和资源的策略如下(代码中也有详细注释):

  • 首先查看类是否已经被加载
  • 然后使用最顶层启动类加载器 BootClassLoader 来加载
  • 然后使用当前类加载器 findClass ,搜索与此类加载器的 dexPath 关联的 dex 文件列表中,是否已经加载
  • 最后才是委托父加载器加载

从代码中可以看到,DelegateLastClassLoader 并没有完全遵循双亲委派模型。

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
public final class DelegateLastClassLoader extends PathClassLoader {
public DelegateLastClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, parent);
}
public DelegateLastClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent) {
super(dexPath, librarySearchPath, parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check whether the class has already been loaded.
// Return it if that's the case.
Class<?> cl = findLoadedClass(name);
if (cl != null) {
return cl;
}

// Next, check whether the class in question
// is present in the boot classpath.
try {
return Object.class.getClassLoader().loadClass(name);
} catch (ClassNotFoundException ignored) {
}

// Next, check whether the class in question is present
// in the dexPath that this classloader operates on.
ClassNotFoundException fromSuper = null;
try {
return findClass(name);
} catch (ClassNotFoundException ex) {
fromSuper = ex;
}
// Finally, check whether the class in question
// is present in the parent classloader.
try {
return getParent().loadClass(name);
} catch (ClassNotFoundException cnfe) {
...
throw fromSuper;
}
}
...
}

BaseDexClassLoader

BaseDexClassLoaderAndroid 平台加载 dex 文件的基类,所有从 dex 文件中查找加载的类 findClass 都是在这里实现。
构造方法中各参数的含义:

  • dexPath :包含 dex 文件的绝对路径列表;文件可以是 apk, jar ,文件列表分隔符为 :
  • optimizedDirectory :没有任何作用,为了兼容早期的版本
  • librarySearchPathnative 库所在文件目录的绝对路径列表;文件目录分隔符默认为 :
  • parent :当前类加载器的父加载器
  • dexFiles :包含 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
38
public class BaseDexClassLoader extends ClassLoader {
...
private final DexPathList pathList;

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath,
librarySearchPath, null);
...
}

public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
// TODO We should support giving this a library search path maybe.
super(parent);
this.pathList = new DexPathList(this, dexFiles);
}

@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(...);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}

public void addDexPath(String dexPath) {
pathList.addDexPath(dexPath, null /*optimizedDirectory*/);
}
...
}

findClass 流程图:

0108-android-classloading-find-class.png

dex 文件加载与解析

DexFile

每个 jar, apk, dex 文件对应一个 DexFile 类实例,它用来将对应的文件加载到虚拟机中。

  • 所有的 dex, jar ,apk 文件,最终都是通过 openDexFile 加载到虚拟机中的
  • 如果是通过 dex 来加载类,最终会走到 defineClass 从虚拟机中加载
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public final class DexFile {
/**
* If close is called, mCookie becomes null but the internal cookie
* is preserved if the close failed so that
* we can free resources in the finalizer.
*/
private Object mCookie;
private Object mInternalCookie;
private final String mFileName;
...
public Class loadClass(String name, ClassLoader loader) {
String slashName = name.replace('.', '/');
return loadClassBinaryName(slashName, loader, null);
}

public Class loadClassBinaryName(String name, ClassLoader loader,
List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}

private static Class defineClass(String name, ClassLoader loader,
Object cookie, DexFile dexFile, List<Throwable> suppressed){
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}

private static Object openDexFile(String sourceName,
String outputName, int flags, ClassLoader loader,
DexPathList.Element[] elements) throws IOException {
// Use absolute paths to enable the use of
// relative paths when testing on host.
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null)
? null
: new File(outputName).getAbsolutePath(),
flags,
loader,
elements);
}

private static Object openInMemoryDexFile(ByteBuffer buf)
throws IOException {
if (buf.isDirect()) {
return createCookieWithDirectBuffer(buf,
buf.position(), buf.limit());
} else {
return createCookieWithArray(buf.array(),
buf.position(), buf.limit());
}
}
private static native Class defineClassNative(String name,
ClassLoader loader, Object cookie, DexFile dexFile);
private static native Object openDexFileNative(String sourceName,
String outputName, int flags, ClassLoader loader,
DexPathList.Element[] elements);
private static native Object createCookieWithDirectBuffer(
ByteBuffer buf, int start, int end);
private static native Object createCookieWithArray(byte[] buf,
int start, int end);
...
}

DexPathList

内部类:

  • Element
    可能叫 DexElement 更合适,但是由于历史原因,可能会存在反射调用此类的情形。每个 Element 对应一个 DexFile 文件。
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
/*package*/ static class Element {
/**
* A file denoting a zip file (in case of a resource jar or a dex jar),
* or a directory (only when dexFile is null).
*/
private final File path;
private final DexFile dexFile;
private ClassPathURLStreamHandler urlHandler;
private boolean initialized;
...
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null
? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}

public URL findResource(String name) {
for (Element element : dexElements) {
URL url = element.findResource(name);
if (url != null) {
return url;
}
}

return null;
}

public Enumeration<URL> findResources(String name) {
ArrayList<URL> result = new ArrayList<URL>();

for (Element element : dexElements) {
URL url = element.findResource(name);
if (url != null) {
result.add(url);
}
}

return Collections.enumeration(result);
}
}
  • NativeLibraryElement
    库文件元素,每个 native lib 对应一个 NativeLibraryElement,可能会包含系统库。
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
/**
* Element of the native library path
*/
/*package*/ static class NativeLibraryElement {

private final File path;
//If path denotes a zip file, this denotes a base path inside the zip.
private final String zipDir;
private ClassPathURLStreamHandler urlHandler;
private boolean initialized;
...
public String findNativeLibrary(String name) {
maybeInit();

if (zipDir == null) {
String entryPath = new File(path, name).getPath();
if (IoUtils.canOpenReadOnly(entryPath)) {
return entryPath;
}
} else if (urlHandler != null) {
// Having a urlHandler means the element has a zip file.
// In this case Android supports loading the library iff
// it is stored in the zip uncompressed.
String entryName = zipDir + '/' + name;
if (urlHandler.isEntryStored(entryName)) {
return path.getPath() + zipSeparator + entryName;
}
}

return null;
}
...
}

DexPathList 是对两个内部类的一个封装,表示每个对象可以包含多个可执行文件 dex, jar, apk 等;同时每个对象可以包含多个库文件等。

  • Elements[] 数组包含了所有的 dex, jar, apk 文件,热补丁技术通常是从这里 inject
  • findClass 最终通过 DexFile 从虚拟机中加载
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*package*/ final class DexPathList {
...
private final ClassLoader definingContext;
private Element[] dexElements;
private final NativeLibraryElement[] nativeLibraryPathElements;
...
public DexPathList(ClassLoader definingContext,
ByteBuffer[] dexFiles) {
...
this.definingContext = definingContext;
...
this.nativeLibraryPathElements =
makePathElements(this.systemNativeLibraryDirectories);
...
this.dexElements =
makeInMemoryDexElements(dexFiles, suppressedExceptions);
...
}

public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
...
this.definingContext = definingContext;
...
this.dexElements = makeDexElements(splitDexPath(dexPath),
optimizedDirectory, suppressedExceptions, definingContext);
...
List<File> allNativeLibraryDirectories =
new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
this.nativeLibraryPathElements =
makePathElements(allNativeLibraryDirectories);
...
}

private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
List<IOException> suppressedExceptions) {
Element[] elements = new Element[dexFiles.length];
int elementPos = 0;
for (ByteBuffer buf : dexFiles) {
try {
DexFile dex = new DexFile(buf);
elements[elementPos++] = new Element(dex);
} catch (IOException suppressed) {
...
}
}
if (elementPos != elements.length) {
elements = Arrays.copyOf(elements, elementPos);
}
return elements;
}

private static Element[] makeDexElements(List<File> files,
File optimizedDirectory, List<IOException> suppressedExceptions,
ClassLoader loader) {
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();
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
DexFile dex = loadDexFile(file,
optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] =
new Element(dex, null);
}
} catch (IOException suppressed) {
...
}
} else {
DexFile dex = null;
try {
dex = loadDexFile(file, optimizedDirectory,
loader, elements);
} catch (IOException suppressed) {
...
}

if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
} else {
...
}
}
...
return elements;
}

private static DexFile loadDexFile(File file, File optimizedDirectory,
ClassLoader loader, Element[] elements)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
} else {
String optimizedPath =
optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(),
optimizedPath, 0, loader, elements);
}
}

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

private static NativeLibraryElement[] makePathElements(
List<File> files) {
NativeLibraryElement[] elements =
new NativeLibraryElement[files.size()];
int elementsPos = 0;
for (File file : files) {
String path = file.getPath();

if (path.contains(zipSeparator)) {
String split[] = path.split(zipSeparator, 2);
File zip = new File(split[0]);
String dir = split[1];
elements[elementsPos++] =
new NativeLibraryElement(zip, dir);
} else if (file.isDirectory()) {
elements[elementsPos++] =
new NativeLibraryElement(file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}

public Class<?> findClass(String name, List<Throwable> suppressed) {
// 类查找过程是变量 Elements 数组,只要查到了就返回
// 热补丁正是利用这个特性,将 patch 插入 Elements 数组的第一个中
// 即使原先包内有 bug 但不会被加载
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;
}

...
}

Android 预加载

系统开机启动会在 ZygoteInit 进程创建 Java 环境,预加载系统常用类,并创建系统的类加载器 PathClassLoader

启动路径和系统服务路径

启动路径 BOOTCLASSPATH 和系统服务路径 SYSTEMSERVERCLASSPATH 最终都是写入 ./root/init.environ.rc 文件的。

  • BOOTCLASSPATH
    编译系统中 PRODUCT_BOOT_JARS 中添加的 jar 包名和路径对应生成的,包含所有 framework 相关 jar 包路径。PRODUCT_BOOT_JARS 最终被编译添加到 BOOTCLASSPATH 变量中,组建过程:

    1
    2
    3
    4
    5
    ./device/qcom/common/base.mk
    ./build/make/core/envsetup.mk
    ./build/make/target/product/core_minimal.mk
    ./device/qcom/common/common.mk
    ./device/qcom/custom/custom.mk
  • SYSTEMSERVERCLASSPATH
    build/make/target/product/core_minimal.mk 文件中定义的 PRODUCT_SYSTEM_SERVER_JARS 变量,会在 system/core/rootdir/Android.mk 中生成 SYSTEMSERVERCLASSPATH 变量。

./root/init.environ.rc 文件中这两个变量的内容为:

1
2
export BOOTCLASSPATH /system/framework/com.qualcomm.qti.camera.jar:/system/framework/QPerformance.jar:/system/framework/core-oj.jar:/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/bouncycastle.jar:/system/framework/apache-xml.jar:/system/framework/legacy-test.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/org.apache.http.legacy.boot.jar:/system/framework/android.hidl.base-V1.0-java.jar:/system/framework/android.hidl.manager-V1.0-java.jar:/system/framework/qcrilhook.jar:/system/framework/hiqmi.jar:/system/framework/qcnvitems.jar:/system/framework/telephony-qmi.jar:/system/framework/tcmiface.jar:/system/framework/WfdCommon.jar:/system/framework/oem-services.jar:/system/framework/qcom.fmradio.jar:/system/framework/telephony-ext.jar
export SYSTEMSERVERCLASSPATH /system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar:/system/framework/com.android.location.provider.jar

预加载类 preloadClasses

预加载的类大概有 4500+ 个系统常用类,采用的是空间换时间的策略:系统开机时就将常用类加载,后续应用使用时不用重复加载,提高应用运行速度。
设置预加载的文件为 preloaded-classes
AOSP 源码路径为:preloaded-classes: frameworks/base/config/preloaded-classes
编译完后会被拷贝到:./system/etc/preloaded-classes ;文件中指定需要加载的常见系统类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
android.app.Activity$HostCallbacks
android.app.ActivityManager
android.app.ActivityManager$1
android.app.ActivityManager$AppTask
...
android.app.ActivityManager$TaskDescription$1
android.app.ActivityOptions
android.app.ActivityThread
android.app.ActivityThread$1
...
android.app.ContentProviderHolder$1
android.app.ContextImpl
android.app.ContextImpl$1
...
android.app.DexLoadReporter
android.app.Dialog
android.app.Dialog$ListenersHandler
android.app.DialogFragment
android.app.DownloadManager
android.app.Fragment
android.app.Fragment$1
...

预加载类流程图:

0108-android-classloading-prloaded-classes.png

创建系统类加载器

Android 默认的系统类加载器为 PathClassLoader ,也是在系统启动阶段 ZygoteInit 中创建的,并加载系统服务的 jar
创建系统类加载器 PathClassLoader ,并加载系统服务 jar 文件流程图:

0108-android-classloading-create-pathclassloader.png

ZygoteInit 处理系统服务进程中,除了生成系统类加载器 PathClassLoader ,并会通过该加载器反射调用 SystemServer.main 方法,启动系统服务进程 system_server 进程,管理整个系统的所有服务。

APK 及四大组件加载过程

APK 加载过程

LoadedApk 类是整个应用 APK 加载的入口类,最终在类加载器工厂中 ClassLoaderFactory.java 创建具体的加载器 PathClassLoader

1
2
3
4
5
6
7
8
9
10
11
// ClassLoaderFactory.java
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, String classloaderName){
if (isPathClassLoaderName(classloaderName)) {
return new PathClassLoader(dexPath, librarySearchPath, parent);
} else if (isDelegateLastClassLoaderName(classloaderName)) {
return new DelegateLastClassLoader(dexPath,librarySearchPath,parent);
}

throw new AssertionError("Invalid classLoaderName: " + classloaderName);
}

调用序列图:

0108-android-classloading-load-apk.png

部分 Log 打印:

  • ZygoteInit 新建应用进程
  • ActivityThread 线程启动进入 main 入口:AMS.attachApplication 启动应用以及进入 Looper.loop() 循环接受主线程消息
  • AMS.attachApplication 会调用 ActivityThread.bindApplication 启动并绑定该应用
  • ContextImpl 第一次加载应用时,调用 PathClassLoader 加载整个应用 APK
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
// 1. AMS.attachApplication
*: V/ActivityManager(1696): New app record ProcessRecord{5515589 3528:com.*.knowledge/u0a76} thread=android.os.BinderProxy@ca7598e pid=3528, processName = com.*.knowledge, appInfo = ApplicationInfo{8c27ebf com.*.knowledge}
*: V/ActivityManager(1696): java.lang.RuntimeException
*: V/ActivityManager(1696): at com.android.server.am.ActivityManagerService.attachApplicationLocked(ActivityManagerService.java:7258)
*: V/ActivityManager(1696): at com.android.server.am.ActivityManagerService.attachApplication(ActivityManagerService.java:7377)
*: V/ActivityManager(1696): at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:291)
*: V/ActivityManager(1696): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2971)
*: V/ActivityManager(1696): at android.os.Binder.execTransact(Binder.java:697)
// 2. AMS.bindApplication
*: V/ActivityThread(3528): bindApplication: processName = com.*.knowledge, instrumentationName = null, appInfo = ApplicationInfo{159d166 com.*.knowledge}
*: V/ActivityThread(3528): java.lang.RuntimeException
*: V/ActivityThread(3528): at android.app.ActivityThread$ApplicationThread.bindApplication(ActivityThread.java:919)
*: V/ActivityThread(3528): at android.app.IApplicationThread$Stub.onTransact(IApplicationThread.java:374)
*: V/ActivityThread(3528): at android.os.Binder.execTransact(Binder.java:697)
...
*: W/ActivityManager(1696): Slow operation: 133ms so far, now at attachApplicationLocked: after mServices.attachApplicationLocked
*: W/Looper(1696): Dispatch took 135ms on android.ui, h=Handler (com.android.server.am.ActivityManagerService$UiHandler) {782d25f} cb=null msg=53
// 3. ActivityThread.handleBindApplication 处理应用绑定
*: V/ActivityThread(3528): Class path: /data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/base.apk, JNI path: /data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/lib/arm64
// 4. PathClassLoader 调用过程堆栈
*: E/System(3528): XMT, sourceName = /data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/base.apk, outputName = null, classloader = dalvik.system.PathClassLoader[null]
*: E/System(3528): java.lang.Exception
*: E/System(3528): at dalvik.system.DexFile.openDexFile(DexFile.java:354)
*: E/System(3528): at dalvik.system.DexFile.<init>(DexFile.java:100)
*: E/System(3528): at dalvik.system.DexFile.<init>(DexFile.java:74)
*: E/System(3528): at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374)
*: E/System(3528): at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337)
*: E/System(3528): at dalvik.system.DexPathList.<init>(DexPathList.java:157)
*: E/System(3528): at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:65)
*: E/System(3528): at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:64)
*: E/System(3528): at com.android.internal.os.ClassLoaderFactory.createClassLoader(ClassLoaderFactory.java:73)
*: E/System(3528): at com.android.internal.os.ClassLoaderFactory.createClassLoader(ClassLoaderFactory.java:88)
*: E/System(3528): at android.app.ApplicationLoaders.getClassLoader(ApplicationLoaders.java:69)
*: E/System(3528): at android.app.ApplicationLoaders.getClassLoader(ApplicationLoaders.java:35)
*: E/System(3528): at android.app.LoadedApk.createOrUpdateClassLoaderLocked(LoadedApk.java:693)
*: E/System(3528): at android.app.LoadedApk.getClassLoader(LoadedApk.java:727)
*: E/System(3528): at android.app.LoadedApk.getResources(LoadedApk.java:954)
*: E/System(3528): at android.app.ContextImpl.createAppContext(ContextImpl.java:2270)
*: E/System(3528): at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5658)
*: E/System(3528): at android.app.ActivityThread.-wrap1(Unknown Source:0)
*: E/System(3528): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1663)
*: E/System(3528): at android.os.Handler.dispatchMessage(Handler.java:106)
*: E/System(3528): at android.os.Looper.loop(Looper.java:164)
*: E/System(3528): at android.app.ActivityThread.main(ActivityThread.java:6522)
*: E/System(3528): at java.lang.reflect.Method.invoke(Native Method)
*: E/System(3528): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
*: E/System(3528): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

Application 类加载过程

ActivityThread.handleBindApplication 除了加载 APK 外,还加载了 Application 类并启动这个应用;最终调用 Instrumentation.newApplication 来加载 Application 类的:

1
2
3
4
5
6
7
// Instrumentation.java
public Application newApplication(ClassLoader cl, String className,
Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return newApplication(cl.loadClass(className), context);
}

调用序列图:

0108-android-classloading-load-application.png

部分 Log 打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Application 类加载过程
*: E/System(23352): XMT, ClassLoader.loadClass, name = com.*.knowledge.KnowledgeApplication, resolve = false
*: E/System(23352): XMT, ClassLoader.findLoadedClass, name = com.*.knowledge.KnowledgeApplication, this = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/lib/arm64, /system/lib64, /vendor/lib64]]]
*: E/System(23352): java.lang.Exception
*: E/System(23352): at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:735)
*: E/System(23352): at java.lang.ClassLoader.loadClass(ClassLoader.java:364)
*: E/System(23352): at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
*: E/System(23352): at android.app.Instrumentation.newApplication(Instrumentation.java:1088)
*: E/System(23352): at android.app.LoadedApk.makeApplication(LoadedApk.java:983)
*: E/System(23352): at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5734)
*: E/System(23352): at android.app.ActivityThread.-wrap1(Unknown Source:0)
*: E/System(23352): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1663)
*: E/System(23352): at android.os.Handler.dispatchMessage(Handler.java:106)
*: E/System(23352): at android.os.Looper.loop(Looper.java:164)
*: E/System(23352): at android.app.ActivityThread.main(ActivityThread.java:6522)
*: E/System(23352): at java.lang.reflect.Method.invoke(Native Method)
*: E/System(23352): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
*: E/System(23352): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
*: E/System(23352): XMT, ClassLoader.loadClass, c = class com.*.knowledge.KnowledgeApplication, parent = java.lang.BootClassLoader@b40a08c

Activity 类加载过程

不管是在启动应用时启动主 Activity ,还是当前 Activity 打开另一个 Activity 时,最终都是通过 ActivityStackSupervisor.realStartActivityLocked 来调用 Activity.scheduleLaunchActivity 来启动指定 Activity ;而每个 Activity 是在 Instrumentation.newActivity 中来加载的:

1
2
3
4
5
6
7
// Instrumentation.java
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}

调用序列图:

0108-android-classloading-load-activity.png

部分 Log 打印:

  • 上半部分为启动主 Activity
  • 下半部分为打开另一个 Activity
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
// 1. 启动主 Activity
*: V/ActivityManager(1508): realStartActivityLocked, r = ActivityRecord{4d4a239 u0 com.*.knowledge/.MainActivity t57}
*: V/ActivityManager(1508): java.lang.RuntimeException
*: V/ActivityManager(1508): at com.android.server.am.ActivityStackSupervisor.realStartActivityLocked(ActivityStackSupervisor.java:1466)
*: V/ActivityManager(1508): at com.android.server.am.ActivityStackSupervisor.attachApplicationLocked(ActivityStackSupervisor.java:983)
*: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.attachApplicationLocked(ActivityManagerService.java:7310)
*: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.attachApplication(ActivityManagerService.java:7377)
*: V/ActivityManager(1508): at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:291)
*: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2971)
*: V/ActivityManager(1508): at android.os.Binder.execTransact(Binder.java:697)
...
// Activity 类加载过程
*: V/ActivityThread(4162): scheduleLaunchActivity, info = ActivityInfo{a9454ac com.*.knowledge.MainActivity}
*: D/:XMT:KnowApplication:(4162): onCreate:
*: E/System(4162): XMT, ClassLoader.loadClass, name = com.*.knowledge.MainActivity, resolve = false
*: E/System(4162): XMT, ClassLoader.findLoadedClass, name = com.*.knowledge.MainActivity, this = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/lib/arm64, /system/lib64, /vendor/lib64]]]
*: E/System(4162): java.lang.Exception
*: E/System(4162): at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:735)
*: E/System(4162): at java.lang.ClassLoader.loadClass(ClassLoader.java:364)
*: E/System(4162): at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
*: E/System(4162): at android.app.Instrumentation.newActivity(Instrumentation.java:1175)
*: E/System(4162): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2678)
*: E/System(4162): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2865)
*: E/System(4162): at android.app.ActivityThread.-wrap11(Unknown Source:0)
*: E/System(4162): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1598)
*: E/System(4162): at android.os.Handler.dispatchMessage(Handler.java:106)
*: E/System(4162): at android.os.Looper.loop(Looper.java:164)
*: E/System(4162): at android.app.ActivityThread.main(ActivityThread.java:6524)
*: E/System(4162): at java.lang.reflect.Method.invoke(Native Method)
*: E/System(4162): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
*: E/System(4162): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
*: E/System(4162): XMT, ClassLoader.loadClass, c = class com.*.knowledge.MainActivity, parent = java.lang.BootClassLoader@11b61a
...
// 2. 应用已经启动后,打开其他 Activity 流程
*: V/ActivityManager(1508): realStartActivityLocked, r = ActivityRecord{6a96a51 u0 com.*.knowledge/.fourcomponents.ShowService t57}
*: V/ActivityManager(1508): java.lang.RuntimeException
*: V/ActivityManager(1508): at com.android.server.am.ActivityStackSupervisor.realStartActivityLocked(ActivityStackSupervisor.java:1466)
*: V/ActivityManager(1508): at com.android.server.am.ActivityStackSupervisor.startSpecificActivityLocked(ActivityStackSupervisor.java:1589)
*: V/ActivityManager(1508): at com.android.server.am.ActivityStack.resumeTopActivityInnerLocked(ActivityStack.java:2752)
*: V/ActivityManager(1508): at com.android.server.am.ActivityStack.resumeTopActivityUncheckedLocked(ActivityStack.java:2265)
*: V/ActivityManager(1508): at com.android.server.am.ActivityStackSupervisor.resumeFocusedStackTopActivityLocked(ActivityStackSupervisor.java:2103)
*: V/ActivityManager(1508): at com.android.server.am.ActivityStack.completePauseLocked(ActivityStack.java:1496)
*: V/ActivityManager(1508): at com.android.server.am.ActivityStack.activityPausedLocked(ActivityStack.java:1423)
*: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.activityPaused(ActivityManagerService.java:7634)
*: V/ActivityManager(1508): at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:317)
*: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2971)
*: V/ActivityManager(1508): at android.os.Binder.execTransact(Binder.java:697)
*: V/ActivityThread(4162): scheduleLaunchActivity, info = ActivityInfo{59de479 com.*.knowledge.fourcomponents.ShowService}

Service 类加载过程

不管 Service 是否设置新进程,也不管是 startService 还是 bindService ,系统都是在 ActivityThread.handleCreateService 中加载 Service 类的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ActivityThread.java
private void handleCreateService(CreateServiceData data) {
...
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
...
}
...
}

bindService 的调用序列图:

0108-android-classloading-load-service.png

部分 Log 打印:

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
// 1. ComtextImpl.bindService 开始进入 AMS
*: V/ActivityManager(1508): requestServiceBindingLocked
*: V/ActivityManager(1508): java.lang.RuntimeException
*: V/ActivityManager(1508): at com.android.server.am.ActiveServices.requestServiceBindingLocked(ActiveServices.java:1856)
*: V/ActivityManager(1508): at com.android.server.am.ActiveServices.requestServiceBindingsLocked(ActiveServices.java:2260)
*: V/ActivityManager(1508): at com.android.server.am.ActiveServices.realStartServiceLocked(ActiveServices.java:2335)
*: V/ActivityManager(1508): at com.android.server.am.ActiveServices.bringUpServiceLocked(ActiveServices.java:2187)
*: V/ActivityManager(1508): at com.android.server.am.ActiveServices.bindServiceLocked(ActiveServices.java:1442)
*: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.bindService(ActivityManagerService.java:18625)
*: V/ActivityManager(1508): at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:584)
*: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2971)
*: V/ActivityManager(1508): at android.os.Binder.execTransact(Binder.java:697)
// 2. 调用 ActivityThread.scheduleBindService
*: V/ActivityThread(4162): scheduleBindService
*: V/ActivityThread(4162): java.lang.RuntimeException
*: V/ActivityThread(4162): at android.app.ActivityThread$ApplicationThread.scheduleBindService(ActivityThread.java:863)
*: V/ActivityThread(4162): at android.app.IApplicationThread$Stub.onTransact(IApplicationThread.java:439)
*: V/ActivityThread(4162): at android.os.Binder.execTransact(Binder.java:697)
// 3. Service 类加载过程
*: E/System(4162): XMT, ClassLoader.loadClass, name = com.*.knowledge.fourcomponents.BindService, resolve = false
*: E/System(4162): XMT, ClassLoader.findLoadedClass, name = com.*.knowledge.fourcomponents.BindService, this = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/lib/arm64, /system/lib64, /vendor/lib64]]]
*: E/System(4162): java.lang.Exception
*: E/System(4162): at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:735)
*: E/System(4162): at java.lang.ClassLoader.loadClass(ClassLoader.java:364)
*: E/System(4162): at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
*: E/System(4162): at android.app.ActivityThread.handleCreateService(ActivityThread.java:3330)
*: E/System(4162): at android.app.ActivityThread.-wrap4(Unknown Source:0)
*: E/System(4162): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1686)
*: E/System(4162): at android.os.Handler.dispatchMessage(Handler.java:106)
*: E/System(4162): at android.os.Looper.loop(Looper.java:164)
*: E/System(4162): at android.app.ActivityThread.main(ActivityThread.java:6524)
*: E/System(4162): at java.lang.reflect.Method.invoke(Native Method)
*: E/System(4162): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
*: E/System(4162): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
*: E/System(4162): XMT, ClassLoader.loadClass, c = class com.*.knowledge.fourcomponents.BindService, parent = java.lang.BootClassLoader@11b61a
*: D/:XMT:BindService:(4162): onCreate:
*: D/:XMT:BindService:(4162): onBind:

静态注册 BroadcastReceiver 类加载过程

静态注册的广播接收器是在 ActivityThread.handleReceiver 中响应广播事件,并加载对应的 BroadcastReceiver 类的:

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
// ActivityThread.java
private void handleReceiver(ReceiverData data) {
...
String component = data.intent.getComponent().getClassName();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
IActivityManager mgr = ActivityManager.getService();
Application app;
BroadcastReceiver receiver;
ContextImpl context;
try {
app = packageInfo.makeApplication(false, mInstrumentation);
context = (ContextImpl) app.getBaseContext();
if (data.info.splitName != null) {
context = (ContextImpl) context.createContextForSplit(
data.info.splitName);
}
java.lang.ClassLoader cl = context.getClassLoader();
data.intent.setExtrasClassLoader(cl);
data.intent.prepareToEnterProcess();
data.setExtrasClassLoader(cl);
receiver=(BroadcastReceiver)cl.loadClass(component).newInstance();
} catch (Exception e) {
...
}
...
}

完整版本可以参考 四大组件 – Broadcast ,简化后的调用序列图:

0108-android-classloading-load-broadcast-receiver.png

部分 Log 打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
06-23 10:47:50.276: E/System(19446): XMT, ClassLoader.loadClass, name = com.staqu.essentials.notifications.NotificationActivationReceiver, resolve = false
06-23 10:47:50.276: E/System(19446): XMT, ClassLoader.findLoadedClass, name = com.staqu.essentials.notifications.NotificationActivationReceiver, this = dalvik.system.PathClassLoader[DexPathList[[zip file "/system/priv-app/SalesTracker/SalesTracker.apk"],nativeLibraryDirectories=[/system/priv-app/SalesTracker/lib/arm, /system/fake-libs, /system/priv-app/SalesTracker/SalesTracker.apk!/lib/armeabi-v7a, /system/lib, /vendor/lib, /system/lib, /vendor/lib]]]
06-23 10:47:50.277: E/System(19446): java.lang.Exception
06-23 10:47:50.277: E/System(19446): at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:735)
06-23 10:47:50.277: E/System(19446): at java.lang.ClassLoader.loadClass(ClassLoader.java:364)
06-23 10:47:50.277: E/System(19446): at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
06-23 10:47:50.277: E/System(19446): at android.app.ActivityThread.handleReceiver(ActivityThread.java:3173)
06-23 10:47:50.277: E/System(19446): at android.app.ActivityThread.-wrap17(Unknown Source:0)
06-23 10:47:50.277: E/System(19446): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1679)
06-23 10:47:50.277: E/System(19446): at android.os.Handler.dispatchMessage(Handler.java:106)
06-23 10:47:50.277: E/System(19446): at android.os.Looper.loop(Looper.java:164)
06-23 10:47:50.277: E/System(19446): at android.app.ActivityThread.main(ActivityThread.java:6522)
06-23 10:47:50.277: E/System(19446): at java.lang.reflect.Method.invoke(Native Method)
06-23 10:47:50.277: E/System(19446): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
06-23 10:47:50.277: E/System(19446): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
06-23 10:47:50.277: E/System(19446): XMT, ClassLoader.loadClass, c = class com.staqu.essentials.notifications.NotificationActivationReceiver, parent = java.lang.BootClassLoader@b40a08c

Android 动态类加载

示例:动态加载 assets 目录下的 dex 文件,并反射调用指定类中的某个方法,返回值在当前 Activity 中显示。

生成 dex 文件

AS 中新建 Test.java 文件,编译生成对应的 Test.class,使用工具生成对应的 test.jar 文件:d8.bat --output=test.jar Test.class
Test.java, Test.class, test.jar 三个文件剪切到 assets 目录下,避免将 Test.java 打包到当前 APK 中了。

1
2
3
4
5
6
7
8
9
package com.*.knowledge.classloading;

public class Test {

public String test(){
return "Test: from other dex file, classLoader: "
+ Test.class.getClassLoader();
}
}

当前 Activity 动态加载

  • 使用异步任务 AsyncTask 实现动态加载
  • 避免内存泄露,定义静态内部类 LoaderAsyncTask ,并使用弱引用指向当前 Activity
  • assets 目录下的 dex 文件,拷贝到 cache 目录下,方便动态加载
  • 使用 DexClassLoader 动态加载 cache 目录下的 dex 文件,指定父加载器为 null
  • 使用反射调用 test 方法,并在当前 Activity 中显示结果
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.*.knowledge.classloading;
...
public class ClassLoadingActivity extends AppCompatActivity {
...
// 避免内存泄露,定义静态内部类
private static class LoaderAsyncTask extends
AsyncTask<String, Void, String>{
// 使用弱引用指向当前 Activity
private WeakReference<ClassLoadingActivity> mActivity;

public LoaderAsyncTask(ClassLoadingActivity activity){
mActivity = new WeakReference<ClassLoadingActivity>(activity);
}

@Override
protected void onPreExecute() {
super.onPreExecute();
mActivity.get().mTvResult.setText("...");
}

@Override
protected String doInBackground(String... strings) {
...
String destFilePath =
mActivity.get().getCacheDir().getAbsolutePath()
+ File.separator + strings[0];
File destFile = new File(destFilePath);
if (!destFile.exists()) {
copyFile(mActivity.get().getApplicationContext(),
strings[0], destFile);
}
...
Log.d(TAG, "doInBackground: destFilePath = " + destFilePath);
try {
// 动态加载 dex 文件,父加载器为 null
DexClassLoader dcl = new DexClassLoader(destFilePath, null,
null, /*mActivity.get().getClassLoader()*/ null);
// 反射加载类,并实例化
Class<?> clazz =
dcl.loadClass("com.*.knowledge.classloading.Test");
Object object = clazz.newInstance();
Method testMethod = clazz.getMethod("test");
// 反射调用指定方法
Object result = testMethod.invoke(object, null);
Log.d(TAG, "doInBackground: result=" + result.toString());
return result.toString();
} catch (...){...}
...
}
}

// 拷贝 assets 下指定文件到内存中指定目标文件
private static void copyFile(Context context,
String fileName, File destFile){
InputStream in=null;
OutputStream out=null;

try {
context = context.getApplicationContext();
in=context.getAssets().open(fileName);
out=new FileOutputStream(destFile.getAbsolutePath());
byte[] bytes=new byte[1024];
int len=0;
while ((len=in.read(bytes))!=-1)
out.write(bytes,0,len);
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (in!=null)
in.close();
if (out!=null)
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

这里需要注意下:DexClassLoader 动态加载时,指定父加载器为 null;依据双亲委派模型,只有父加载器加载失败时,才会使用当前加载器加载。
这里假定: Test.java 在当前 APK 存在,并且父加载器使用的是 PathClassLoader 。依据双亲委派模型会优先使用 PathClassLoader 加载 Test 类,而我们指定的 DexClassLoader 并不能动态加载 assets 目录下的 Test

示例 Log

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
52
53
*: I/mmid(518): select timeout: wait for receiving msg
*: D/XMT:ClassLoadingAct(16046): doInBackground:
// 1. 将 assets 目录下的 dex 文件,拷贝到 cache 目录下
*: D/XMT:ClassLoadingAct(16046): copyFile: fileName = test.jar
*: D/XMT:ClassLoadingAct(16046): doInBackground: destFilePath = /data/user/0/com.*.knowledge/cache/test.jar
// 2. 动态加载 dex 文件
*: E/System(16046): XMT, openDexFile, sourceName = /data/user/0/com.*.knowledge/cache/test.jar, outputName = null, flags = 0, classloader = dalvik.system.DexClassLoader[null]
*: E/System(16046): java.lang.Exception
*: E/System(16046): at dalvik.system.DexFile.openDexFile(DexFile.java:356)
*: E/System(16046): at dalvik.system.DexFile.<init>(DexFile.java:100)
*: E/System(16046): at dalvik.system.DexFile.<init>(DexFile.java:74)
*: E/System(16046): at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374)
*: E/System(16046): at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337)
*: E/System(16046): at dalvik.system.DexPathList.<init>(DexPathList.java:157)
*: E/System(16046): at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:65)
*: E/System(16046): at dalvik.system.DexClassLoader.<init>(DexClassLoader.java:54)
*: E/System(16046): at com.*.knowledge.classloading.ClassLoadingActivity$LoaderAsyncTask.doInBackground(ClassLoadingActivity.java:85)
*: E/System(16046): at com.*.knowledge.classloading.ClassLoadingActivity$LoaderAsyncTask.doInBackground(ClassLoadingActivity.java:53)
*: E/System(16046): at android.os.AsyncTask$2.call(AsyncTask.java:333)
*: E/System(16046): at java.util.concurrent.FutureTask.run(FutureTask.java:266)
*: E/System(16046): at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
*: E/System(16046): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
*: E/System(16046): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
*: E/System(16046): at java.lang.Thread.run(Thread.java:764)
*: E/System(16046): XMT, element: null
*: I/dex2oat(16078): The ClassLoaderContext is a special shared library.
*: I/dex2oat(16078): /system/bin/dex2oat --debuggable --debuggable --dex-file=/data/data/com.*.knowledge/cache/test.jar --output-vdex-fd=64 --oat-fd=65 --oat-location=/data/data/com.*.knowledge/cache/oat/arm64/test.odex --compiler-filter=quicken --class-loader-context=&
*: I/InputReader(1509): Reconfiguring input devices. changes=0x00000010
*: I/Telecom(1509): DefaultDialerCache: Refreshing default dialer for user 0: now com.android.dialer: DDC.oR@AGc
*: W/VoiceInteractionManagerService(1509): no available voice interaction services found for user 0
*: D/CarrierSvcBindHelper(2041): No carrier app for: 0
*: D/CarrierSvcBindHelper(2041): No carrier app for: 1
*: D/ImsResolver(2041): maybeAddedImsService, packageName: com.*.knowledge
*: I/dex2oat(16078): dex2oat took 934.000ms (1.609s cpu) (threads: 1000) arena alloc=1432B (1432B) java alloc=16KB (16400B) native alloc=492KB (504744B) free=2MB (2116696B)
*: I/zygote64(16046): The ClassLoaderContext is a special shared library.
// 3. 加载指定类 Test
*: E/System(16046): XMT, ClassLoader.loadClass, name = com.*.knowledge.classloading.Test, resolve = false
*: E/System(16046): XMT, ClassLoader.findLoadedClass, name = com.*.knowledge.classloading.Test, this = dalvik.system.DexClassLoader[DexPathList[[zip file "/data/user/0/com.*.knowledge/cache/test.jar"],nativeLibraryDirectories=[/system/lib64, /vendor/lib64]]]
*: E/System(16046): java.lang.Exception
*: E/System(16046): at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:735)
*: E/System(16046): at java.lang.ClassLoader.loadClass(ClassLoader.java:364)
*: E/System(16046): at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
*: E/System(16046): at com.*.knowledge.classloading.ClassLoadingActivity$LoaderAsyncTask.doInBackground(ClassLoadingActivity.java:86)
*: E/System(16046): at com.*.knowledge.classloading.ClassLoadingActivity$LoaderAsyncTask.doInBackground(ClassLoadingActivity.java:53)
*: E/System(16046): at android.os.AsyncTask$2.call(AsyncTask.java:333)
*: E/System(16046): at java.util.concurrent.FutureTask.run(FutureTask.java:266)
*: E/System(16046): at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
*: E/System(16046): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
*: E/System(16046): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
*: E/System(16046): at java.lang.Thread.run(Thread.java:764)
*: E/System(16046): XMT, ClassLoader.loadClass, c = class com.*.knowledge.classloading.Test, parent = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.*.knowledge-8K_G8HZhGrHEo_X_5AJuhg==/base.apk"],nativeLibraryDirectories=[/data/app/com.*.knowledge-8K_G8HZhGrHEo_X_5AJuhg==/lib/arm64, /system/lib64, /vendor/lib64]]]
// 4. 反射调用指定方法 test
*: D/XMT:ClassLoadingAct(16046): doInBackground: result = Test: from other dex file, classLoader: dalvik.system.DexClassLoader[DexPathList[[zip file "/data/user/0/com.*.knowledge/cache/test.jar"],nativeLibraryDirectories=[/system/lib64, /vendor/lib64]]]

Log 中可以看出,Test.test 方法是 DexClassLoader 加载的,对应路径为 /data/user/0/com.*.knowledge/cache/test.jar

Android 热补丁

Android 动态加载中,我们把 Test.java 从源码中删除了,也就是说 APK 中并不包含这个类,所以通过反射来访问的。接下来我们介绍 Android 热补丁,也就是 Test.javaAPK 中存在,我们需要使用 assets 中的补丁替换它。

热补丁原理

Android 的类加载器在加载一个类时,先从当前加载器的 DexPathList 对象中的 Element[] dexElements 数组中,获取对应的类并加载。采用的是数组遍历的方式,遍历每一个 dex 文件;先遍历出来的是 dex 文件,先加载 class,成功加载后就不再遍历后续 dex 文件。
热修复的原理就是:将补丁 class 打包成 dex 文件后,放到 Element 数组的第一个元素,这样就能保证获取到的 class 是最新修复好的 class了。

0108-android-classloading-hot-fix.png

参考 Java 类加载机制 ,对于 new TestClass() 这个语句,会触发类初始化过程。而初始化分为两部分:类初始化过程 <cinit> ,即类加载过程的初始化阶段;类实例化过程 <init> 。而类一旦初始化后,后续再 new 只会执行实例化过程,也就是常说的类只会被加载一次,但是会实例化多次。所以热补丁必须要在类初始化 <cinit> 之前合入,否则不会生效。

Test

1
2
3
4
5
6
7
8
9
package com.*.knowledge.classloading;

public class Test {

public String test(){
return "Test: from current APK, classLoader: "
+ Test.class.getClassLoader();
}
}

这个 Test.java 跟随其他代码一起编译到 APK 中,而我们 assets 中的 Test.java 的方法中,返回值不一样 return "Test: from other dex file, classLoader: " ... ,后续通过 Log 和界面显示出来。
如果最终热加载成功,将显示 ... from other dex ... 而不是 ... from current APK ...

当前 Activity 合入热补丁

因为 DexPathList, Element 等都是包内可见,所以只能通过反射将补丁包插入到 Element[] 数组的第一个元素中。

  • 获取系统加载器 PathClassLoader, DexPathList, Elements
  • 获取补丁包中的 DexClassLoader, DexPathList, Elements
  • 将补丁包的 Elements 插入到当前系统加载器的 Elements
  • 后续加载修复类时,会优先从补丁包中获取
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
public class HotFixActivity extends AppCompatActivity {
...
private String getTestResult(){
Test test = new Test();
String result = test.test();
return result;
}

private void handleHotFix(){
HotFixAsyncTask hotFixAsyncTask = new HotFixAsyncTask(this);
hotFixAsyncTask.execute();
}

private static class HotFixAsyncTask extends
AsyncTask<String, Void, String>{
private WeakReference<HotFixActivity> mActivity;
private String mDexFilePath;

public HotFixAsyncTask(HotFixActivity activity){
mActivity = new WeakReference<HotFixActivity>(activity);
}

@Override
protected String doInBackground(String... strings) {
if (isDexFileExist()) {
//hotFix();
return mActivity.get().getTestResult();
}
return "Hot Fix Error!";
}

@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
mActivity.get().mBtnHotFix.setEnabled(true);
mActivity.get().mTvNew.setText(s);
}

private boolean isDexFileExist(){
String dexFileName = "test.jar";
mDexFilePath = mActivity.get().getCacheDir().getAbsolutePath()
+ File.separator + dexFileName;
File destFile = new File(mDexFilePath);
if (!destFile.exists()) {
copyFile(mActivity.get().getApplicationContext(),
dexFileName, destFile);
}
if (!destFile.exists()){
Log.d(TAG, "isDexFileExist: copy error!");
return false;
}
return true;
}

private void hotFix(){
Log.d(TAG, "hotFix: ");
try {
// 1.1 获取系统加载器
PathClassLoader pcl = (PathClassLoader)
mActivity.get().getClassLoader();
// 1.2 使用 DexClassLoader 加载补丁包
DexClassLoader dcl = new DexClassLoader(mDexFilePath,
null, null, pcl);
// 2.1 获取当前已经加载的 DexPathList
Object origDexPathList = getPathList(pcl);
// 2.2 获取当前加载器的 Elements 数组
Object origElements = getDexElements(origDexPathList);
// 3.1 获取补丁包中的 DexPathList
Object patchDexPathList = getPathList(dcl);
// 3.2 获取补丁包中的 Elements 数组
Object patchElements = getDexElements(patchDexPathList);
// 4 将补丁包插入到当前 Elements 数组前面
Object patchedElements =
combineArray(patchElements, origElements);
setDexElements(origDexPathList, patchedElements);
} catch (...)
}

private static void setField(Object object, Class<?> clazz,
String fieldName, Object value)
throws NoSuchFieldException, IllegalAccessException {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}

private static Object getField(Object object, Class<?> clazz,
String fieldName)
throws NoSuchFieldException, IllegalAccessException {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
}

private Object getPathList(ClassLoader cl)
throws NoSuchFieldException, IllegalAccessException {
return getField(cl, cl.getClass().getSuperclass(),
"pathList");
}

private Object getDexElements(Object dexPathList)
throws NoSuchFieldException, IllegalAccessException {
Log.d(TAG, "getDexElements: ");
return getField(dexPathList, dexPathList.getClass(),
"dexElements");
}

private void setDexElements(Object dexPathList, Object elements)
throws NoSuchFieldException, IllegalAccessException {
Log.d(TAG, "setDexElements: ");
setField(dexPathList, dexPathList.getClass(),
"dexElements", elements);
}

private static Object combineArray(Object array1, Object array2) {
Log.d(TAG, "combineArray: ");
if (array1 == null || !array1.getClass().isArray()
|| array2 == null || !array2.getClass().isArray()) {
Log.d(TAG, "combineArray: array is null.");
return null;
}

Class<?> clazz = array1.getClass().getComponentType();
int length1 = Array.getLength(array1);
int length2 = Array.getLength(array2);
int length = length1 + length2;
Object combineArray = Array.newInstance(clazz, length);
System.arraycopy(array1, 0, combineArray, 0, length1);
System.arraycopy(array2, 0, combineArray, length1, length2);
return combineArray;
}

...
}
}

示例 Log

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
* I/mmid: select timeout: wait for receiving msg
*/com.*.knowledge D/XMT:HotFixAct: hotFix:
*/com.*.knowledge E/System: XMT, openDexFile, sourceName = /data/user/0/com.*.knowledge/cache/test.jar, outputName = null, flags = 0, classloader = dalvik.system.DexClassLoader[null]
*/com.*.knowledge E/System: java.lang.Exception
at dalvik.system.DexFile.openDexFile(DexFile.java:356)
at dalvik.system.DexFile.<init>(DexFile.java:100)
at dalvik.system.DexFile.<init>(DexFile.java:74)
at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374)
at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337)
at dalvik.system.DexPathList.<init>(DexPathList.java:157)
at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:65)
at dalvik.system.DexClassLoader.<init>(DexClassLoader.java:54)
at com.*.knowledge.classloading.HotFixActivity$HotFixAsyncTask.hotFix(HotFixActivity.java:108)
at com.*.knowledge.classloading.HotFixActivity$HotFixAsyncTask.doInBackground(HotFixActivity.java:76)
at com.*.knowledge.classloading.HotFixActivity$HotFixAsyncTask.doInBackground(HotFixActivity.java:60)
at android.os.AsyncTask$2.call(AsyncTask.java:333)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
* /com.*.knowledge E/System: XMT, element: null
* /com.*.knowledge I/zygote64: The ClassLoaderContext is a special shared library.
* /com.*.knowledge D/XMT:HotFixAct: getPathList:
* /com.*.knowledge D/XMT:HotFixAct: getDexElements:
* /com.*.knowledge D/XMT:HotFixAct: getPathList:
* /com.*.knowledge D/XMT:HotFixAct: getDexElements:
* /com.*.knowledge D/XMT:HotFixAct: combineArray:
* /com.*.knowledge D/XMT:HotFixAct: setDexElements:
* /com.*.knowledge D/XMT:HotFixAct: getTestResult: result = Test: from other dex file, classLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/user/0/com.*.knowledge/cache/test.jar", zip file "/data/app/com.*.knowledge-0dRhS8t5SzW66qvx7ecm8w==/base.apk"],nativeLibraryDirectories=[/data/app/com.*.knowledge-0dRhS8t5SzW66qvx7ecm8w==/lib/arm64, /system/lib64, /vendor/lib64]]]

从结果可以看出 Test 类是从补丁包中加载的:

  • 类加载器为系统加载器 PathClassLoader,也就是它的 Elements 数组已经通过反射合入了热补丁
  • DexPathList 中包含两个文件:补丁包 /data/user/0/com.*.knowledge/cache/test.jar 和原始的 APK: /data/app/com.*.knowledge-0dRhS8t5SzW66qvx7ecm8w==/base.apk

其他

dalvikvm

类似 PC 端的 java 工具,用来指执行 apk,dex,jarAndroid 可执行文件的,常用参数为:

  • -cp :指定可执行文件绝对路径
  • -verbose:class :在 logcat 中输出类加载过程
1
2
3
4
5
6
7
8
9
10
11
xmt@server005:~/$ dalvikvm --help
Unknown argument: --help
dalvikvm: [options] class [argument ...]

The following standard options are supported:
-classpath classpath (-cp classpath)
-Dproperty=value
-verbose:tag ('gc', 'jit', 'jni', or 'class')
-showversion
-help
...

下面是一个调试过程,可以看到 Android 类加载过程

  • java 文件编写

    1
    2
    3
    4
    5
    6
    public class DalvikvmTest {

    public static void main(String[] args) {
    System.out.println("This is DalvikvmTest.");
    }
    }
  • class 文件生成
    javac -bootclasspath C:\Users\xmt\sdk\platforms\android-28\android.jar DalvikvmTest.java ,这里可以省略 -bootclasspath ,只有在调用了 android 代码时才需要。

  • dex, jar 文件生成

    1
    C:\Users\xmt\sdk\build-tools\28.0.3\d8.bat --classpath C:\Users\xmt\sdk\platforms\android-28\android.jar --output=DalvikvmTest.jar DalvikvmTest.class

这里同样可以省略 --classpath

  • push 到手机中,dalvikvm 执行

    1
    2
    3
    adb push DalvikvmTest.jar /storage/emulated/0/test
    adb shell
    dalvikvm -verbose:class -cp /storage/emulated/0/test/DalvikvmTest.jar DalvikvmTest
  • 对应 log
    Log 中可以清晰的看到类的加载和类初始化两个过程,默认使用的是 PathClassLoader 类加载器。

    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
    /? I/dalvikvm: Adding image space took 153.906us
    /? I/dalvikvm: Adding image space took 57.448us
    /? I/dalvikvm: Adding image space took 71.718us
    ...
    /? W/ADebug: Failed to get property persist.sys.media.traces
    /? I/dalvikvm: Initialized class Landroid/system/OsConstants; from /system/framework/core-libart.jar
    /? I/dalvikvm: Initialized class Ljava/io/FileDescriptor; from /system/framework/core-oj.jar
    /? I/dalvikvm: Initialized class Ljava/net/Inet4Address; from /system/framework/core-oj.jar
    /? I/dalvikvm: Initialized class Ljava/lang/System; from /system/framework/core-oj.jar
    /? I/dalvikvm: Initialized class Ljava/net/Inet6Address; from /system/framework/core-oj.jar
    /? I/dalvikvm: Initialized class Ljava/io/UnixFileSystem; from /system/framework/core-oj.jar
    /? I/dalvikvm: Initialized class Ljava/io/File; from /system/framework/core-oj.jar
    /? I/dalvikvm: Initialized class Ljava/util/regex/Pattern; from /system/framework/core-oj.jar
    /? I/dalvikvm: Loaded class [Ljava/lang/ThreadLocal$ThreadLocalMap$Entry;
    /? E/System: XMT, openDexFile, sourceName = /storage/emulated/0/test/DalvikvmTest.jar, outputName = null, flags = 0, classloader = dalvik.system.PathClassLoader[null]
    /? E/System: java.lang.Exception
    at dalvik.system.DexFile.openDexFile(DexFile.java:356)
    at dalvik.system.DexFile.<init>(DexFile.java:100)
    at dalvik.system.DexFile.<init>(DexFile.java:74)
    at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374)
    at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337)
    at dalvik.system.DexPathList.<init>(DexPathList.java:157)
    at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:65)
    at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:64)
    at java.lang.ClassLoader.createSystemClassLoader(ClassLoader.java:224)
    at java.lang.ClassLoader.-wrap0(Unknown Source:0)
    at java.lang.ClassLoader$SystemClassLoader.<clinit>(ClassLoader.java:183)
    at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1110)
    /? E/System: XMT, element: null
    /? I/dalvikvm: The ClassLoaderContext is a special shared library.
    /? I/dalvikvm: Registering /storage/emulated/0/test/oat/arm64/DalvikvmTest.odex
    /? I/dalvikvm: Initialized class Ljava/lang/ClassLoader$SystemClassLoader; from /system/framework/core-oj.jar
    // 加载 DalvikvmTest 类
    /? I/dalvikvm: Loaded class LDalvikvmTest; from /storage/emulated/0/test/DalvikvmTest.jar
    /? I/dalvikvm: Beginning verification for class: DalvikvmTest in /storage/emulated/0/test/DalvikvmTest.jar
    /? I/dalvikvm: Class preverified status for class DalvikvmTest in /storage/emulated/0/test/DalvikvmTest.jar: 1
    // DalvikvmTest 类初始化
    /? I/dalvikvm: Initialized class LDalvikvmTest; from /storage/emulated/0/test/DalvikvmTest.jar
    /? I/dalvikvm: Initialized class Llibcore/icu/NativeConverter; from /system/framework/core-libart.jar
    /? I/dalvikvm: Initialized class Ljava/nio/charset/StandardCharsets; from /system/framework/core-oj.jar
    /? I/dalvikvm: Loaded class Ljava/io/BufferedWriter; from /system/framework/core-oj.jar
    /? I/dalvikvm: Beginning verification for class: java.io.BufferedWriter in /system/framework/core-oj.jar
    /? I/dalvikvm: Class preverified status for class java.io.BufferedWriter in /system/framework/core-oj.jar: 1
    /? I/dalvikvm: Initialized class Ljava/io/BufferedWriter; from /system/framework/core-oj.jar

AOSP libcore 编译

在调试时,希望打出类加载堆栈中调用关系,在 DexFile.java 中增加了一个 Log 打印:System.logE("XMT, ***" , new Exception()); 。修改完代码后,在 libcore 目录下编译 mm ,但是总是会出错并打印如下信息:

1
2
ninja: error: 'out/host/common/obj/JAVA_LIBRARIES/junit-hostdex_intermediates/classes.jack', needed by 'out/host/common/obj/JAVA_LIBRARIES/core-test-rules-hostdex_intermediates/classes.jack', missing and no known rule to make it
11:21:10 ninja failed with: exit status 1

根据错误信息,需要编译 core-test-rules 模块,其实我们并不需要。在 libcore/JavaLibrary.mk 中看到这个模块是宏 LIBCORE_SKIP_TESTS 控制的,我们在 libcore/Android.mk 中注释掉这个宏,export LIBCORE_SKIP_TESTS = false ,重新编译。Java 代码的改动会更新:

1
2
3
4
out/***/system/framework/core-libart.jar
out/***/system/framework/boot-core-libart.vdex
out/***/system/framework/boot-core-libart.oat
out/***/system/framework/boot-core-libart.art

将这几个文件 push 到手机后重启生效。

小结:

  • 打印 log 方式:使用 System.logE()
  • 编译方式:设置 export LIBCORE_SKIP_TESTS = false ,在 libcore/ 目录下 mm

常见问题

APK 加载过程

应用中的四大组件和自定义类,编译工具会先将 Java 文件生成 .class ,然后合并成 .dex 文件,最终打包成 APKAndroid 启动 APP 时,会加载对应的 APK 文件,并将整个 APK 中相关类信息加载到虚拟机中,参考 APK 加载过程章节中的序列图。

Android 类加载时机

APK 加载后,并不会将拥有的类全部加载,只有在类初始化时才会加载,类加载时机参考 Java 类加载机制 ,大概有 5 种情况会触发。

  • APK 中的四大组件是主动调用 ClassLoader 来加载,通过反射来实例化的
  • APK 中普通自定义类通过 new 来加载和实例化;这个过程并没有出现 ClassLoader 类加载器相关调用关系,怀疑是虚拟机 ART 中直接实现(没有看虚拟机的实现代码只是猜测)
  • APK 中如果遇到了 framework 中的类,会通过双亲委派模型从系统中 ClassLoader.findLoadedClass 查找已经加载的类
  • 虚拟机 ART 中的底层代码,在加载应用中的类时,会在 Java DexPathList.Elements 数组中查找对应的 apk, dex, jar 等文件,并加载对应类(并没有搞清楚底层代码是怎么实现的,但是从热补丁的测试结果来看是这样的)

打印类加载过程

可以通过 dalvikvm -verbose:class 简单了解类加载过程。

几个重要的 native 方法

  • DexFile.openDexFile -> openDexFileNative
  • DexFile.defineClass -> defineClassNative
  • Class.classForName
  • ClassLoader.findLoadedClass -> VMClassLoader.findLoadedClass

后续

  • 热加载,插件化详细分析
  • ART 底层代码分析

参考文档

0%