Android源码解析-App的ClassLoader

Android中的默认类加载器:

Android虚拟机就是一个特殊的JVM,不管是以前的dalvik还是现在的ART。所以其类加载流程一样遵循jvm的规则(双亲委托机制)

如果对jvm的类加载流程不熟悉可以阅读另一篇文章:java类加载原理(ClassLoader工作原理)

Android类加载器之相较于Java类加载器的最大区别在于:

java类加载器是从某个目录中去直接加载.class文件或者是从目录下的zip、jar包等归档文件中加载.class文件

而Android类加载器是从某个目录下去直接加载.dex文件或者是从目录下的zip、jar、apk等文件中的.dex文件中去加载.class文件,如果不是.dex文件的话加载不了。

以上类关系图清楚的描述了Android中的默认类加载器以及它们的父子关系。
图中有涉及到了DexPathList这个类简单来说是存放该类加载器的加载区域中的所有dex文件,在Android的插件化方案和热修复方案中经常会使用将补丁包或者插件包中的dex插入这个数组的第一个来实现热修复或者加载插件的功能。具体详情可看另一个文章:android热修复小记

app中的类加载器

在Android开发中例如在Activity中会调用getClassLoader()来获取类加载器,这时候这个类加载器是怎么来的呢?来分析一下

Activity类中并没有getClassLoader(),从继承关系上可以知道最终调用的是ContextWrapper.getClassLoader(),最终的实现在ContextImpl中。如果不知道的话可以看下我另外一篇文章:Android源码小记-Context解析,可以清楚了解Context的继承关系以及Activity和Context之间是如何关联以及与ContextImpl之间的联系。

1
2
3
4
5
6
ContextImpl.java

public ClassLoader getClassLoader() {
return mPackageInfo != null ?
mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
}

这个mPackageInfo对象是一个LoadedApk,持有apk的所有信息,是apk在内存中的表现形态。(ANdroid源码小记-LoadedApk),而它是在构造ContextImpl的时候传进来并赋值的。

在启动Activity时,最终会走ActivityThread中的handleLaunchActivity()方法,这个方法里就会最终调用到先知性Instrumentation.newActivity()通过类加载器创建一个Activity实例,然后调用createBaseContextForActivity()去创建一个处理提供Activity运行环境的ContextImpl,此时就会给mPackageInfo赋值。

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
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
int displayId = Display.DEFAULT_DISPLAY;
try {
displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

// 创建提供Activity运行环境的ContextImpl用于之后attach给Activity,此时传入的LoadedApk是r.packageInfo
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.token, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;

final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
String pkgName = SystemProperties.get("debug.second-display.pkg");
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
if (id != Display.DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getDisplayAdjustments(id));
baseContext = appContext.createDisplayContext(display);
break;
}
}
}
return baseContext;
}

这个r.packageInfo其实就是在应用刚启动时在ActivityThread.handleBindApplication()方法中的data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); 这里创建的,这时候创建出一个表示本应用的LoadedApk,正常情况下全局都共用这个LoadedApk对象,创建Activity的ContextImpl时也不例外。

回到ContextImpl.getClassLoader()中,mPackageInfo已经知道怎么来了,那再来看代码:

1
2
3
4
public ClassLoader getClassLoader() {
return mPackageInfo != null ?
mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
}

此时mPackageInfo不为空走mPackageInfo.getClassLoader().

1
2
3
4
5
6
7
8
public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader == null) {
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
}
return mClassLoader;
}
}

正常走到这里因为我们的应用报名肯定不是”android”,因为packageName.equals(“android”)是系统本身,所以我们最终只能走到createOrUpdateClassLoaderLocked()方法:

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
private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
if (mPackageName.equals("android")) {
// 系统的情况不看,我们走不到
...

return;
}

...
if (!mIncludeCode) {
if (mClassLoader == null) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
// 创建ClassLoader
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(
"" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
librarySearchPath, libraryPermittedPath, mBaseClassLoader);
StrictMode.setThreadPolicy(oldPolicy);
}

return;
}

...
if (mClassLoader == null) {
...

mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
libraryPermittedPath, mBaseClassLoader);

...
}

if (addedPaths != null && addedPaths.size() > 0) {
final String add = TextUtils.join(File.pathSeparator, addedPaths);
// 添加主工程的源码路径
ApplicationLoaders.getDefault().addPath(mClassLoader, add);
// Setup the new code paths for profiling.
needToSetupJitProfiles = true;
}

...
}

最后的最后还是通过ApplicationLoaders.getClassLoader()来创建:

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
public ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
String librarySearchPath, String libraryPermittedPath,
ClassLoader parent) {
// 传入的是系统ClassLoader,阅读源码其实是PathClassLoader,可以看ClassLoader.createSystemClassLoader(),其parent是BootClassLoader,所有此时baseParent = BootClassLoader
ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();

synchronized (mLoaders) {
if (parent == null) {
parent = baseParent;
}


// 如果是lib库中的类返回的加载区域为lib目录的类加载器
if (parent == baseParent) {
ClassLoader loader = mLoaders.get(zip);
if (loader != null) {
return loader;
}

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);

PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(
zip,
librarySearchPath,
libraryPermittedPath,
parent,
targetSdkVersion,
isBundled);

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupVulkanLayerPath");
setupVulkanLayerPath(pathClassloader, librarySearchPath);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

mLoaders.put(zip, pathClassloader);
return pathClassloader;
}

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
// 应用主工程中的类返回的是加载区域为apk的sourceDir所在目录的PathClassLoader
PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return pathClassloader;
}
}

最后可以得出结论:首先反正返回的肯定是PathClassLoader,调用getClassLoader()的类属于不同的位置(主工程或者lib工程)则创建的PathClassLoader对应的加载区域也是不同的,具体要看住工程的加载区域可以跟踪一下,在创建的时候传入的zip是””,则它是通过addPath()方法传入的,该方法在BaseDexClassLoader中,而创建出来的PathClassLoader调用这个方法的地方是在ApplicationLoader.addPath():

1
2
3
4
5
6
7
void addPath(ClassLoader classLoader, String dexPath) {
if (!(classLoader instanceof PathClassLoader)) {
throw new IllegalStateException("class loader is not a PathClassLoader");
}
final PathClassLoader baseDexClassLoader = (PathClassLoader) classLoader;
baseDexClassLoader.addDexPath(dexPath);
}

该方法在刚才的LoadedApk.createOrUpdateClassLoaderLocked()中:

1
2
3
4
5
6
if (addedPaths != null && addedPaths.size() > 0) {
final String add = TextUtils.join(File.pathSeparator, addedPaths);
ApplicationLoaders.getDefault().addPath(mClassLoader, add);
// Setup the new code paths for profiling.
needToSetupJitProfiles = true;
}

继续追溯add参数的来源是LoadedApk.makePaths(),最后的结论就是这个路径是ApplicationInfo的sourceDir。

小结:

在Activity中调用getClassLoader()返回的是PathClassLoader,它的父类加载器是系统的类加载器也是个PathClassLoader,再往上是BootClassLoader。

温馨提示:

要清楚明白的知道ActivityThread、Activity、Application、Context、LoadedApk、Instrumentation这些类的关系对阅读Android源码很有帮助。