Android App 启动时的操作之 ClassLoader 和 Application 初始化
公共部分
-
ActivityManagerService.startProcessLocked()
当
app
启动时,ActivityManagerService.startProcessLocked()
是app
启动时启动进程的地方。 -
ZygoteInit.java
的过程startSystemServer()
方法 ->Zygote.forkSystemServer()
-
RuntimeInit.invokeStaticMain()
反射调回到ActivityThread.main(...)
//反射调用 ActivityThread.main(...) m = cl.getMethod("main", new Class[] { String[].class });
上面做了一个简述。
Zygote
的fork
分析,在这里没有细说。需要的可以参考链接:
理解Android进程创建流程
Application 和 ClassLoader 部分
1. 补充部分-有关 ClassLoader
在 app
里,系统类加载器有三种: BootClassLoader
, PathClassLoader
, DexClassLoader
。
-
BootClassLoader
是用来加载系统 framework 层级的类加载器, 同时它也是 app 中所有 ClassLoader 的最顶层的 parentAndroid系统启动时会使用
BootClassLoader
来预加载常用类是个单例。是
ClassLoader
的一个私有内部类 PathClassLoader
是用来加载应用程序的类, 通常是加载已经安装好的 apk 文件-
DexClassLoader
可以加载 dex 文件以及包含 dex 的 apk 文件(安装好的程序和未安装的 dex 文件)实际上
DexClassLoader
的加载范围比PathClassLoader
的加载范围要大, 它可以加载在 SD 上面的.jar
和.apk
文件。在一些动态修复,补丁包上面,是利用
DexClassLoader
去实现的。
现在引出一个问题,Application 的 类加载器 是哪个??
代码检验一下:
ClassLoader classLoader = baseContext.getClassLoader();
if (classLoader != null) {
Log.i(TAG, "classLoader is " + classLoader.toString() + " --->from Log");
while (classLoader.getParent() != null) {
classLoader = classLoader.getParent();
Log.i(TAG, "classLoader is " + classLoader.toString() + " --->from Log in while");
}
}
打印结果为:
ThemeLayoutContainer: classLoader is dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.chenzhao.thememaintest-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.chenzhao.thememaintest-1/lib/arm, /data/app/com.example.chenzhao.thememaintest-1/base.apk!/lib/armeabi-v7a, /vendor/lib, /system/lib]]] --->from Log
ThemeLayoutContainer: classLoader is java.lang.BootClassLoader@f42ce93 --->from Log in while
从上面可以看到, 加载 application 的类加载器为 dalvik.system.PathClassLoader
, 并且它与包名 com.example.chenzhao.thememaintest-1/base.apk
是相关的,也就是一对一对应的。
为什么会有 parent
这个属性?
ClassLoader
的 loadclass()
采用的是 (双)父亲委托模型, 在 BootClassLoader
是 PathClassLoader
的父亲,同时它也是最顶层的一个 classLoader
.(脑补树的图)
loadclass()
的步骤:加载类 ATest.class
-
会先查询当前
ClassLoader
(AClassLoader
) 是否加载过ATest
,加载过就返回;注: 注意是加载过!!
如果没有,查询 它 (
AClassLoader
) 的parent
是否已经加载过ATest
,如果有,就直接返回parent
加载过的类, 如果没有,依次向上去寻找它的parent
;如果继承路线上的
ClassLoader
都没有加载,则会用它 (AClassLoader
) 去加载该类ATest
;-
当一个类被位于树根 的
ClassLoader
加载过,那么, 在以后整个系统的生命周期内,这个类永远不会被重新加载树根的
ClassLoader
在Android
系统中是BootClassLoader
2. 正式开始, 那么,PathClassLoader 是什么时候初始化的呢?
一切都需要从 ActivityThread.main(...) 说起
当一个进程被创建成功后,会走到 ActivityThread.main(...)
,
//ActivityThread.java
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
上述代码,主要的操作在 thread.attach(false);
调用次序:
-
thread.attach(false)
;这是第一步,如图上所示
1. thread.attach(false)
: 大致代码如下:final IActivityManager mgr = ActivityManager.getService(); //在这里 mgr 是 AMS(ActivityManagerService) try { mgr.attachApplication(mAppThread); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); }
关键代码:调用了
mgr.attachApplication(...)
; -
mgr.attachApplication(mAppThread)
;mgr
是IActivityManager
,在这里是ActivityManagerService
, 即ActivityManagerService.attachApplication(mAppThread)
;那么需要去 AMS 里面寻找对应相关代码.
-
ActivityManagerService.attachApplicationLocked(thread, callingPid);
在它的里面,会调用
thread.bindApplication()
thread 为传过来的参数,是在
ActivityThread
里面的一个成员变量, 它的赋值是在初始化时完成的,代码如下:// ActivityThread 里面 final ApplicationThread mAppThread = new ApplicationThread();
是
ApplicationThread
的一个对象,它是ActivityThread
的内部类,再次回到ActivityThread
, 去找thread.bindApplication()
这个方法,进入第 4 步; -
ApplicationThread.bindApplication()
在这方法里面,最主要的代码就是
sendMessage(H.BIND_APPLICATION, data);
发了一条
message
(是主线程的handler
发送的消息), 在handleMessage(Message)
里面处理了该消息。AppBindData data = (AppBindData)msg.obj; handleBindApplication(data);
注:在
handleMessage(Message)
这个方法里面可以看到,是主线程的处理位置,里面有很多管理activity
生命周期的方法和参数, 它便是主线程处理消息的地方。
-
ActivityThread.handleBindApplication(data)
这个方法太长了,太长了!主要代码看下面的部分:
Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; ... //其实这一步是调用的 application 的 onCreate() 方法. 如时序图中的 mInstrumentation.callApplicationOnCreate(app);
data.info
是什么? 在这里是LoadedApk
这个对象, 这个类是一个比较重要的类,可以多关注下.注:这里稍微注意一下
mInitialApplication
赋值的操作 -
LoadedApk.makeApplication(..., null);
在
LoadedApk.makeApplication()
里面的代码会去获取ClassLoader
, 并且创建appContext
, 再通过ClassLoader
和appContext
去创建application
对象;try { java.lang.ClassLoader cl = getClassLoader(); if (!mPackageName.equals("android")) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "initializeJavaContextClassLoader"); initializeJavaContextClassLoader(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { ...
终于看到了有关
ClassLoader
的部分。下面具体看ClassLoader
是如何创建的。 如图上右上角。 -
LoadedApk.getClassLoader()
首先假设这个时候
mClassLoader
是空的,然后去看一下ClassLoader
的创建:(其实这个时候已经被创建出来了)public ClassLoader getClassLoader() { synchronized (this) { if (mClassLoader == null) { createOrUpdateClassLoaderLocked(null /*addedPaths*/); } return mClassLoader; } }
注:实际上
LoadedApk.getClassLoader()
在这里并不为 null,ClassLoader
真正创建是在程序的前面,这里暂不做分析. -
LoadedApk.createOrUpdateClassLoaderLocked(null)
在这个方法里面,
LoadedApk
的mIncludeCode = true;
会走到:
if (mClassLoader == null) { // Temporarily disable logging of disk reads on the Looper thread // as this is early and necessary. StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, mBaseClassLoader); StrictMode.setThreadPolicy(oldPolicy); // Setup the class loader paths for profiling. needToSetupJitProfiles = true; }
那么接下来,看一下
ApplicationLoaders.getDefault().getClassLoader(...)
注:
mIncludeCode
是一个标志位,去加载 APK 时是否需要加载它的源码。mIncludeCode
在一些比较hack
的方式去获取其他 apk 的context
时,true
和false
是有区别的。 -
ApplicationLoaders
这个类对于
ApplicationLoaders
这个类,是个单例,它里面维护了一个mLoaders
, 它是一个map
,key
为string
(可以看做是 包名),value
为ClassLoader
(类加载器),看一下
getClassLoader()
的实现:public ClassLoader getClassLoader(String zip, ... ) { ... // 首先 检查 mLoaders map 里 是否有该 loader, 有即返回, //没有则创建一个新的 pathClassLoader, 并把新建的 loader 加入 mLoaders ClassLoader loader = mLoaders.get(zip); if (loader != null) { return loader; } PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(...); ... mLoaders.put(zip, pathClassloader); return pathClassloader; ... }
到这里为止,一个 app 对应的进程的
ClassLoader
才被创建成功.注:这里应该注意到
ApplicationLoaders
里面是可以对应保存多个 APK 的ClassLoader
的
上述 1~9 是 app 启动时的一系列动作,7~9 分析的是 ClassLoader 的创建过程。下面我们看一下 Application 的创建过程。
从第 6 条开始:
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
如图上右边 Application 创建时序图
:
-
Instrumentation
是什么?Instrumentation
是android
系统里面的一套控制方法或者“钩子”,我理解的是,它可以在正常的生命周期(正常是有系统控制的)之外控制android
的运行, 拿到一些流程控制的时机,然后我们就可以在这些时机里面写一些处理我们想要的东西。它提供了各种流程控制方法,例如下面的方法:callActivityOnCreate()-----> 对应着系统的 onCreate(); callActivityOnStart()-----> 对应着系统的 onStart(); callActivityOnDestroy()-----> 对应着系统的 onDestroy();
所以 当我们去新建一个类, 继承与
Instrumentation
, 重写一些方法,在这些方法里面我们就可以自由的做一些控制的事情,例如,添加自动测试模块等。Instrumentation.newApplication(cl, appClass, appContext)
的三个参数:cl
根据上面可知道,它是ClassLoader
-
appClass
当前 appapplication
的名字:appClass = mApplicationInfo.className;
-
appContext: 是当前这个 app 对应的 context
作为参数去创建 application 时, 在 application.attach(context), 把该 context 作为 application 的 context.
-
Instrumentation.newApplication(...)
代码:
return newApplication(cl.loadClass(className), context);
本质上是通过
ClassLoader
根据路径名去loadClass(className)
。结合 ClassLoader 的
loadClass()
的逻辑.VMClassLoader
-
Instrumentation.newApplication(clazz, context);
代码:
Application app = (Application)clazz.newInstance(); app.attach(context); return app;
实例话
application
对象,然后赋值给app
,return
.上述实际上已经完成了 application 对象的建立
-
Application 的
app.attach(context)
是做什么?跳转到
Application
中,进入到代码里可以看到:/* package */ final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; }
手动调用了
attachBaseContext(context)
这个方法。注:手动跳转到
ContextWrapper.attachBaseContext()
在 Application 的回调方法里面,我们通常会用到的两个方法:
attachBaseContext(context)
onCreate()
当一个
Application
建立起时, 会首先调用attachBaseContext()
这个方法。那么什么时候,才会调用onCreate()
呢?接着去寻找调用
onCreate()
的时机。 -
Application 调用
onCreate()
回到上面的第 5 步:
ActivityThread.handleBindApplication(data)
在其方法里面:
Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; ... try { mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { ...
这个时候,调用了
mInstrumentation.callApplicationOnCreate(app);
, 调用位置标注为 C 处注:
makeApplication(...)
调用时,传入的第二个参数为instrumentation = null
, 在makeApplication(...)
下有关于instrumentation
的判断 -
Instrumentation.callApplicationOnCreate(app)
这个方法里面比较简单:
// app 为 application 对象 app.onCreate();
所以在 Application 里面
attachBaseContext()
是早于onCreate()
的调用的。如图所示:调用
application.attachBaseContext()
的位置是 A, 调用application.onCreate()
的位置是 C.
Application 中 getApplicationContext() 与上述两个方法的关系
在代码调用中,
在
attachBaseContext()
里调用getApplicationContext()
返回的为null
;在
onCreate()
里调用getApplicationContext()
返回的不为null
;
为什么呢? 图示如左下角:
看一下 getApplicationContext()
调用栈:
-
会首先在
ContextWrapper.java
里面getApplicationContext()
:return mBase.getApplicationContext();
-
mBase
是什么?是一个 Context 对象,在
Application.attachBaseContext()
时被赋值的。去
ContextImpl.java
里面去看。 -
ContextImpl.getApplicationContext()
代码如下:
return (mPackageInfo != null) ? mPackageInfo.getApplication() : mMainThread.getApplication();
梳理一下逻辑:
-
mPackageInfo
什么时候赋值的?是否为null
mPackageInfo
是LoadedApk
的对象该
mBase
是在LoadedApk
通过ContextImpl.createAppContext(mActivityThread, this)
里创建的,
mPackageInfo = packageInfo;
出现在ContextImpl
的构造函数里面,不为null
, 为this( 为 LoadedApk)
-
mPackageInfo.getApplication()
返回的是 return mApplication;
mApplication 是什么时候赋值的呢?
-
-
在 mPackageInfo.getApplication() 里 mApplication 的赋值
在 LoadedApk.getApplication() 里面 对 mApplication 的赋值只有一处, 出现在
LoadedApk.makeApplication() 里面
app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); ... mApplication = app;
我们已经知道,在
mActivityThread.mInstrumentation.newApplication(...)
的时候, Application 已经调用了attachBaseContext()
所以当在attachBaseContext()
里面调用getApplicationContext()
时,返回的值为null
.而 mApplication = app; 之后,才执行了
Application.onCreate()
.
总结:
从上述步骤,总结一下,可以看到:
ClassLoader
的创建要早于这个Apk
里所有的Class
;PathClassLoader
是和APK
相互对应的,PathClassLoader
的DexPathList
是与 APK 的安装路径一一对应的;一个进程是可以对应多个
ClassLoader
的。 在ApplicationLoader
里面有一个ArrayMap
;mLoaders Application
的创建是利用ClassLoader
的loadclass()
实现的Application
的attachBaseContext()
是早于onCreate()
的调用的getApplicationContext()
不为null
是晚于attachBaseContext()
, 早于onCreate()
方法的
坑边闲话
关于这部分的内容,有些枯燥,查看源码的过程比较枯燥,这里面的源码具体 api 是 26 还是多少,暂时不定,因为后面你如果自己看源码可能会发现 api 已经修改了,但大体是这样一个流程。
希望能在查看这个流程中得到意外的收获~
当然文中不可避免可能会出现错误,我的一些理解可能不足够准确,希望不会对看到文章的同学造成困扰。
参考链接:
以 ClassLoader 为视角看 Android 应用的启动过程
ClassLoader的来源
理解Android进程创建流程
Android 源码,
Zygote.java
,ActivityManagerServer.java
,Process
,ZygoteInit.java
,ActivityThread.java
,LoadedApk.java
,ApplicationLoaders.java