1.安卓应用启动过程以及classloader的创建
下面我们基于安卓源码了解一下这三个classloader的实际使用,这对我们后续了解插件化中类的动态加载十分重要。
我们先了解一下安卓apk安装流程:
- 应用安装时首先应用安装请求被发送给系统,通常是通过
PackageInstaller
接口 - PMS解析APK文件,提取出清单文件(AndroidManifest.xml)中的信息,包括应用的权限、组件(Activities、Services、Broadcast Receivers、Content Providers)等。
- PMS为应用分配唯一的用户ID(UID)和组ID(GID),用于隔离应用数据和权限。
- 应用的APK文件及相关资源会被复制到系统指定的目录,通常是
/data/app/
或其细分目录下。 - DEX文件(包含Java字节码)会被优化为机器码,并存放在
dalvik-cache
目录下,以提高运行效率。 - PMS会在系统数据库中记录应用的安装信息,包括包名、版本、安装路径、权限等,以便后续管理和查询。
- 安装完成后,PMS会发送一个安装成功的广播(ACTION_PACKAGE_ADDED),通知系统和其他应用有新应用安装。
从上述简易流程可以看到AndroidManifest.xml
是在应用安装时就已经解析并记录,所以插件的AndroidManifest.xml配置无法生效,每个APK安装都是独享空间的,不同APK、同一个APK的不同时间安装都是完全独立的
我们再了解一下应用启动流程:
- Launcher发起请求:
- 用户在Launcher(即主屏幕应用)上点击一个App图标。
- Launcher通过Binder接口向系统的服务端组件——Activity Manager Service (AMS) 发送一个请求,要求启动一个新的Activity(即目标App的入口Activity)。
- AMS处理请求:
- AMS接收到启动请求后,首先检查目标App的进程是否已经运行。
- 如果目标App的进程尚未启动,AMS会调用
startProcessLocked
函数,向Zygote进程发送请求创建一个新的应用进程。Zygote是一个预初始化的Android进程,用于孵化所有新应用进程。 - Zygote通过fork自身来创建一个新的进程,并在这个新进程中执行目标App的
ActivityThread
类的main
方法,从而初始化App的主线程。
- App进程初始化:
- 在新创建的App进程中,
ActivityThread
的main
方法中调用attach()
方法,初始化了App的运行环境,包括加载库、设置Looper和Handler等。 ActivityThread
随后调用attachApplication
方法,向AMS进行绑定,告知AMS这个进程已经准备好接收任务。
- 在新创建的App进程中,
- Activity的启动:
- AMS收到
attachApplication
消息后,通过IApplicationThread接口的bindApplication
方法通知App进程创建Application对象,并初始化应用的全局状态。 - 紧接着,AMS通过
scheduleTransaction
方法调度启动Activity的事务,实际上调用ActivityThread
的相应方法来实例化目标Activity。 - App进程中,Activity的生命周期方法开始被调用,依次是
onCreate
、onStart
、onResume
等,构建UI并显示给用户。
- AMS收到
ActivityThread中创建Application是通过调用LoadedApk的makeApplication
方法来创建的,我们来跟一下makeApplication的代码
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
final java.lang.ClassLoader cl = getClassLoader();//反射创建Application所使用的ClassLoader
......
}
再看一下getClassLoader
的代码:
public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader == null) {
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
}
return mClassLoader;
}
}
继续看createOrUpdateClassLoaderLocked
private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
........
if (!mIncludeCode) {
if (mDefaultClassLoader == null) {
StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoader(
"" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
librarySearchPath, libraryPermittedPath, mBaseClassLoader,
null /* classLoaderName */);
setThreadPolicy(oldPolicy);
mAppComponentFactory = AppComponentFactory.DEFAULT;
}
if (mClassLoader == null) {
mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
new ApplicationInfo(mApplicationInfo));
}
return;
}
........
}
继续看ApplicationLoaders.getDefault().getClassLoader
最终调用到了这个方法:
private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
String librarySearchPath, String libraryPermittedPath,
ClassLoader parent, String cacheKey,
String classLoaderName, List<ClassLoader> sharedLibraries) {
........
ClassLoader classloader = ClassLoaderFactory.createClassLoader(
zip, librarySearchPath, libraryPermittedPath, parent,
targetSdkVersion, isBundled, classLoaderName, sharedLibraries);
.........
}
}
mLoaders是一个arraymap,它的key为dexPath,value为ClassLoaderFactory.createClassLoader返回的ClassLoader,继续跟ClassLoaderFactory.createClassLoader方法
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, String classloaderName,
List<ClassLoader> sharedLibraries) {
ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)
? null
: sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);
if (isPathClassLoaderName(classloaderName)) {
return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries);
} else if (isDelegateLastClassLoaderName(classloaderName)) {
return new DelegateLastClassLoader(dexPath, librarySearchPath, parent,
arrayOfSharedLibraries);
}
throw new AssertionError("Invalid classLoaderName: " + classloaderName);
}
综上,应用安装时安卓会把我们的apk解压到指定目录,当进程启动时,会通过上述一系列操作把dexpath传给PathClassLoader,所以我们一个应用使用的classloader都是这一个PathClassLoader实例,我们也可以直接在自己的代码中打印任意类型的getClassLoader()验证
2.Android中的classloader
DexClassLoader、PathClassLoader、BaseDexClassLoader是安卓中特有的ClassLoader,其中DexClassLoader、PathClassLoader都是继承自BaseDexClassLoader。
我们直接来看DexClassLoader和PathClassLoader的源码 如下
//最新版本DexClassLoader构造
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
//8.0以前DexClassLoader构造
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
@SystemApi(client = MODULE_LIBRARIES)
public PathClassLoader(
@NonNull String dexPath, @Nullable String librarySearchPath, @Nullable ClassLoader parent,
@Nullable ClassLoader[] sharedLibraryLoaders) {
this(dexPath, librarySearchPath, parent, sharedLibraryLoaders, null);
}
@SystemApi(client = MODULE_LIBRARIES)
public PathClassLoader(
@NonNull String dexPath, @Nullable String librarySearchPath,
@Nullable ClassLoader parent, @Nullable ClassLoader[] sharedLibraryLoaders,
@Nullable ClassLoader[] sharedLibraryLoadersAfter) {
super(dexPath, librarySearchPath, parent, sharedLibraryLoaders, sharedLibraryLoadersAfter);
}
}
从源码中可以看到DexClassLoader构造函数相比PathClassLoader多了一个optimizedDirectory,即优化后的odex路径,但是新版DexClassLoader(安卓8.0以后)并不会把这个路径传给BaseDexClassLoader,所以这两个classloader均可用于加载DEX文件,区别更多在于应用场景的历史习惯和描述,而非本质功能。至于网上常见的 ”PathClassLoader 是用来加载已经安装的 apk 的,DexClassLoader 是用来加载存储空间的 dex / apk 文件的” 的说法其实是有问题的,至于插件化、热修复框架也不一定非要使用DexClassLoader,看到很多框架都是继承PathClassLoader或者BaseDexClassLoader重写findclass方法来实现加载自己的类。
我们再看一下BaseDexClassLoader的源码
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// First, check whether the class is present in our shared libraries.
if (sharedLibraryLoaders != null) {
for (ClassLoader loader : sharedLibraryLoaders) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException ignored) {
}
}
}
// Check whether the class in question is present in the dexPath that
// this classloader operates on.
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);//
if (c != null) {
return c;
}
// Now, check whether the class is present in the "after" shared libraries.
if (sharedLibraryLoadersAfter != null) {
for (ClassLoader loader : sharedLibraryLoadersAfter) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException ignored) {
}
}
}
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
可以看到调用了pathList的findClass方法返回class对象,继续跟代码:
public Class<?> findClass(String name, List<Throwable> suppressed) {
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;
}
可以看到遍历PathList类中dexElements数组, 调用Element.findClass==>DexFile.loadClassBinaryName==>defineClassNative(native方法),返回class
总结:
- ClassLoader种类:重点介绍了
DexClassLoader
、PathClassLoader
以及它们的基类BaseDexClassLoader
。这些类加载器负责从指定路径加载DEX文件,进而加载类到内存中执行。 - DexClassLoader与PathClassLoader差异:早期版本中,
DexClassLoader
额外支持指定优化后的DEX文件存放目录,但后来版本中这一差异已不明显。两者均可用于加载DEX文件,区别更多在于应用场景的历史习惯和描述,而非本质功能。 - BaseDexClassLoader工作原理:深入到
BaseDexClassLoader
的findClass
方法,展示了类加载的逻辑,包括检查共享库、遍历dexElements
数组(由各种DEX文件或包含类定义的路径组成),最终调用到DexFile.loadClassBinaryName
并通过JNI调用到本地方法加载类。
3. 双亲委派机制
最后再简单介绍一下双亲委派机制,一些插件化框架会利用双亲委派机制指定class的加载优先级。
双亲委派机制是Java类加载机制中非常重要的一环,它描述了类加载器在加载类时的一种协作策略。该机制的基本思想是,当一个类加载器收到类加载请求时,首先不会自己尝试加载这个类,而是将这个请求委派给它的父类加载器去完成。只有当父类加载器无法加载该类时(即在父类加载器的搜索范围内未找到所需的类),子加载器才会尝试自己加载该类。这一过程自顶向下传递,直至顶层的启动类加载器(Bootstrap ClassLoader)。如果所有加载器都无法加载,则抛出ClassNotFoundException异常。
双亲委派机制的目的
- 安全性:防止恶意代码篡改系统类。因为用户自定义的类加载器无法加载已经被父类加载器加载过的类,这样就确保了像Java API这样的核心类库不会被篡改或替换。
- 稳定性:确保类加载的唯一性。不同类加载器加载同一个类时,最终都是由顶层的启动类加载器来加载,保证了即使在复杂的类加载器层级结构中,相同的类也不会被重复加载。
- 隔离性:不同应用的类加载器可以加载同名的类而互不影响,这为应用的模块化设计提供了可能,每个模块可以有自己的类加载器,负责加载本模块的类。