Android App 啟動時的操作之 ClassLoader 和 Application 初始化
公共部分
-
ActivityManagerService.startProcessLocked()
當(dāng)
app
啟動時,ActivityManagerService.startProcessLocked()
是app
啟動時啟動進(jìn)程的地方风罩。 -
ZygoteInit.java
的過程startSystemServer()
方法 ->Zygote.forkSystemServer()
-
RuntimeInit.invokeStaticMain()
反射調(diào)回到ActivityThread.main(...)
//反射調(diào)用 ActivityThread.main(...) m = cl.getMethod("main", new Class[] { String[].class });
上面做了一個簡述缠导。
Zygote
的fork
分析趴久,在這里沒有細(xì)說掖鱼。需要的可以參考鏈接:
理解Android進(jìn)程創(chuàng)建流程
Application 和 ClassLoader 部分
1. 補(bǔ)充部分-有關(guān) ClassLoader
在 app
里以清,系統(tǒng)類加載器有三種: BootClassLoader
, PathClassLoader
, DexClassLoader
典格。
-
BootClassLoader
是用來加載系統(tǒng) framework 層級的類加載器廓潜, 同時它也是 app 中所有 ClassLoader 的最頂層的 parentAndroid系統(tǒng)啟動時會使用
BootClassLoader
來預(yù)加載常用類是個單例母怜。是
ClassLoader
的一個私有內(nèi)部類 PathClassLoader
是用來加載應(yīng)用程序的類余耽, 通常是加載已經(jīng)安裝好的 apk 文件-
DexClassLoader
可以加載 dex 文件以及包含 dex 的 apk 文件(安裝好的程序和未安裝的 dex 文件)實際上
DexClassLoader
的加載范圍比PathClassLoader
的加載范圍要大, 它可以加載在 SD 上面的.jar
和.apk
文件。在一些動態(tài)修復(fù)苹熏,補(bǔ)丁包上面碟贾,是利用
DexClassLoader
去實現(xiàn)的。
現(xiàn)在引出一個問題轨域,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");
}
}
打印結(jié)果為:
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
是相關(guān)的朱巨,也就是一對一對應(yīng)的。
為什么會有 parent
這個屬性枉长?
ClassLoader
的 loadclass()
采用的是 (雙)父親委托模型冀续, 在 BootClassLoader
是 PathClassLoader
的父親,同時它也是最頂層的一個 classLoader
.(腦補(bǔ)樹的圖)
loadclass()
的步驟:加載類 ATest.class
-
會先查詢當(dāng)前
ClassLoader
(AClassLoader
) 是否加載過ATest
必峰,加載過就返回洪唐;注: 注意是加載過!!
如果沒有,查詢 它 (
AClassLoader
) 的parent
是否已經(jīng)加載過ATest
吼蚁,如果有凭需,就直接返回parent
加載過的類, 如果沒有,依次向上去尋找它的parent
肝匆;如果繼承路線上的
ClassLoader
都沒有加載粒蜈,則會用它 (AClassLoader
) 去加載該類ATest
;-
當(dāng)一個類被位于樹根 的
ClassLoader
加載過,那么术唬, 在以后整個系統(tǒng)的生命周期內(nèi)薪伏,這個類永遠(yuǎn)不會被重新加載樹根的
ClassLoader
在Android
系統(tǒng)中是BootClassLoader
2. 正式開始, 那么粗仓,PathClassLoader 是什么時候初始化的呢嫁怀?
一切都需要從 ActivityThread.main(...) 說起
當(dāng)一個進(jìn)程被創(chuàng)建成功后设捐,會走到 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);
調(diào)用次序:
-
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(); }
關(guān)鍵代碼:調(diào)用了
mgr.attachApplication(...)
; -
mgr.attachApplication(mAppThread)
;mgr
是IActivityManager
萝招,在這里是ActivityManagerService
, 即ActivityManagerService.attachApplication(mAppThread)
;那么需要去 AMS 里面尋找對應(yīng)相關(guān)代碼.
-
ActivityManagerService.attachApplicationLocked(thread, callingPid);
在它的里面存捺,會調(diào)用
thread.bindApplication()
thread 為傳過來的參數(shù)槐沼,是在
ActivityThread
里面的一個成員變量, 它的賦值是在初始化時完成的,代碼如下:// ActivityThread 里面 final ApplicationThread mAppThread = new ApplicationThread();
是
ApplicationThread
的一個對象捌治,它是ActivityThread
的內(nèi)部類岗钩,再次回到ActivityThread
, 去找thread.bindApplication()
這個方法,進(jìn)入第 4 步; -
ApplicationThread.bindApplication()
在這方法里面肖油,最主要的代碼就是
sendMessage(H.BIND_APPLICATION, data);
發(fā)了一條
message
(是主線程的handler
發(fā)送的消息)兼吓, 在handleMessage(Message)
里面處理了該消息。AppBindData data = (AppBindData)msg.obj; handleBindApplication(data);
注:在
handleMessage(Message)
這個方法里面可以看到森枪,是主線程的處理位置视搏,里面有很多管理activity
生命周期的方法和參數(shù), 它便是主線程處理消息的地方县袱。
-
ActivityThread.handleBindApplication(data)
這個方法太長了浑娜,太長了!主要代碼看下面的部分:
Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; ... //其實這一步是調(diào)用的 application 的 onCreate() 方法. 如時序圖中的 mInstrumentation.callApplicationOnCreate(app);
data.info
是什么式散? 在這里是LoadedApk
這個對象, 這個類是一個比較重要的類筋遭,可以多關(guān)注下.注:這里稍微注意一下
mInitialApplication
賦值的操作 -
LoadedApk.makeApplication(..., null);
在
LoadedApk.makeApplication()
里面的代碼會去獲取ClassLoader
, 并且創(chuàng)建appContext
, 再通過ClassLoader
和appContext
去創(chuàng)建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) { ...
終于看到了有關(guān)
ClassLoader
的部分。下面具體看ClassLoader
是如何創(chuàng)建的杂数。 如圖上右上角宛畦。 -
LoadedApk.getClassLoader()
首先假設(shè)這個時候
mClassLoader
是空的,然后去看一下ClassLoader
的創(chuàng)建:(其實這個時候已經(jīng)被創(chuàng)建出來了)public ClassLoader getClassLoader() { synchronized (this) { if (mClassLoader == null) { createOrUpdateClassLoaderLocked(null /*addedPaths*/); } return mClassLoader; } }
注:實際上
LoadedApk.getClassLoader()
在這里并不為 null,ClassLoader
真正創(chuàng)建是在程序的前面,這里暫不做分析. -
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
是一個標(biāo)志位,去加載 APK 時是否需要加載它的源碼见芹。mIncludeCode
在一些比較hack
的方式去獲取其他 apk 的context
時,true
和false
是有區(qū)別的。 -
ApplicationLoaders
這個類對于
ApplicationLoaders
這個類,是個單例罕邀,它里面維護(hù)了一個mLoaders
, 它是一個map
养距,key
為string
(可以看做是 包名)诉探,value
為ClassLoader
(類加載器),看一下
getClassLoader()
的實現(xiàn):public ClassLoader getClassLoader(String zip, ... ) { ... // 首先 檢查 mLoaders map 里 是否有該 loader棍厌, 有即返回肾胯, //沒有則創(chuàng)建一個新的 pathClassLoader, 并把新建的 loader 加入 mLoaders ClassLoader loader = mLoaders.get(zip); if (loader != null) { return loader; } PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(...); ... mLoaders.put(zip, pathClassloader); return pathClassloader; ... }
到這里為止竖席,一個 app 對應(yīng)的進(jìn)程的
ClassLoader
才被創(chuàng)建成功.注:這里應(yīng)該注意到
ApplicationLoaders
里面是可以對應(yīng)保存多個 APK 的ClassLoader
的
上述 1~9 是 app 啟動時的一系列動作,7~9 分析的是 ClassLoader 的創(chuàng)建過程敬肚。下面我們看一下 Application 的創(chuàng)建過程毕荐。
從第 6 條開始:
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
如圖上右邊 Application 創(chuàng)建時序圖
:
-
Instrumentation
是什么?Instrumentation
是android
系統(tǒng)里面的一套控制方法或者“鉤子”艳馒,我理解的是憎亚,它可以在正常的生命周期(正常是有系統(tǒng)控制的)之外控制android
的運行, 拿到一些流程控制的時機(jī)弄慰,然后我們就可以在這些時機(jī)里面寫一些處理我們想要的東西第美。它提供了各種流程控制方法,例如下面的方法:callActivityOnCreate()-----> 對應(yīng)著系統(tǒng)的 onCreate(); callActivityOnStart()-----> 對應(yīng)著系統(tǒng)的 onStart(); callActivityOnDestroy()-----> 對應(yīng)著系統(tǒng)的 onDestroy();
所以 當(dāng)我們?nèi)バ陆ㄒ粋€類陆爽, 繼承與
Instrumentation
, 重寫一些方法什往,在這些方法里面我們就可以自由的做一些控制的事情,例如墓陈,添加自動測試模塊等恶守。Instrumentation.newApplication(cl, appClass, appContext)
的三個參數(shù):cl
根據(jù)上面可知道,它是ClassLoader
-
appClass
當(dāng)前 appapplication
的名字:appClass = mApplicationInfo.className;
-
appContext: 是當(dāng)前這個 app 對應(yīng)的 context
作為參數(shù)去創(chuàng)建 application 時贡必, 在 application.attach(context), 把該 context 作為 application 的 context.
-
Instrumentation.newApplication(...)
代碼:
return newApplication(cl.loadClass(className), context);
本質(zhì)上是通過
ClassLoader
根據(jù)路徑名去loadClass(className)
兔港。結(jié)合 ClassLoader 的
loadClass()
的邏輯.VMClassLoader
-
Instrumentation.newApplication(clazz, context);
代碼:
Application app = (Application)clazz.newInstance(); app.attach(context); return app;
實例話
application
對象,然后賦值給app
,return
.上述實際上已經(jīng)完成了 application 對象的建立
-
Application 的
app.attach(context)
是做什么仔拟?跳轉(zhuǎn)到
Application
中衫樊,進(jìn)入到代碼里可以看到:/* package */ final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; }
手動調(diào)用了
attachBaseContext(context)
這個方法。注:手動跳轉(zhuǎn)到
ContextWrapper.attachBaseContext()
在 Application 的回調(diào)方法里面利花,我們通常會用到的兩個方法:
attachBaseContext(context)
onCreate()
當(dāng)一個
Application
建立起時科侈, 會首先調(diào)用attachBaseContext()
這個方法。那么什么時候炒事,才會調(diào)用onCreate()
呢臀栈?接著去尋找調(diào)用
onCreate()
的時機(jī)。 -
Application 調(diào)用
onCreate()
回到上面的第 5 步:
ActivityThread.handleBindApplication(data)
在其方法里面:
Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; ... try { mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { ...
這個時候挠乳,調(diào)用了
mInstrumentation.callApplicationOnCreate(app)权薯;
, 調(diào)用位置標(biāo)注為 C 處注:
makeApplication(...)
調(diào)用時,傳入的第二個參數(shù)為instrumentation = null
, 在makeApplication(...)
下有關(guān)于instrumentation
的判斷 -
Instrumentation.callApplicationOnCreate(app)
這個方法里面比較簡單:
// app 為 application 對象 app.onCreate();
所以在 Application 里面
attachBaseContext()
是早于onCreate()
的調(diào)用的睡扬。如圖所示:調(diào)用
application.attachBaseContext()
的位置是 A, 調(diào)用application.onCreate()
的位置是 C.
Application 中 getApplicationContext() 與上述兩個方法的關(guān)系
在代碼調(diào)用中盟蚣,
在
attachBaseContext()
里調(diào)用getApplicationContext()
返回的為null
;在
onCreate()
里調(diào)用getApplicationContext()
返回的不為null
;
為什么呢? 圖示如左下角:
看一下 getApplicationContext()
調(diào)用棧:
-
會首先在
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)
里創(chuàng)建的蔼两,
mPackageInfo = packageInfo;
出現(xiàn)在ContextImpl
的構(gòu)造函數(shù)里面,不為null
如孝, 為this( 為 LoadedApk)
-
mPackageInfo.getApplication()
返回的是 return mApplication;
mApplication 是什么時候賦值的呢宪哩?
-
-
在 mPackageInfo.getApplication() 里 mApplication 的賦值
在 LoadedApk.getApplication() 里面 對 mApplication 的賦值只有一處, 出現(xiàn)在
LoadedApk.makeApplication() 里面
app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); ... mApplication = app;
我們已經(jīng)知道,在
mActivityThread.mInstrumentation.newApplication(...)
的時候第晰, Application 已經(jīng)調(diào)用了attachBaseContext()
所以當(dāng)在attachBaseContext()
里面調(diào)用getApplicationContext()
時锁孟,返回的值為null
.而 mApplication = app; 之后,才執(zhí)行了
Application.onCreate()
.
總結(jié):
從上述步驟茁瘦,總結(jié)一下品抽,可以看到:
ClassLoader
的創(chuàng)建要早于這個Apk
里所有的Class
;PathClassLoader
是和APK
相互對應(yīng)的,PathClassLoader
的DexPathList
是與 APK 的安裝路徑一一對應(yīng)的;一個進(jìn)程是可以對應(yīng)多個
ClassLoader
的甜熔。 在ApplicationLoader
里面有一個ArrayMap<String, ClassLoader> mLoaders
;Application
的創(chuàng)建是利用ClassLoader
的loadclass()
實現(xiàn)的Application
的attachBaseContext()
是早于onCreate()
的調(diào)用的getApplicationContext()
不為null
是晚于attachBaseContext()
圆恤, 早于onCreate()
方法的
坑邊閑話
關(guān)于這部分的內(nèi)容,有些枯燥腔稀,查看源碼的過程比較枯燥盆昙,這里面的源碼具體 api 是 26 還是多少,暫時不定焊虏,因為后面你如果自己看源碼可能會發(fā)現(xiàn) api 已經(jīng)修改了淡喜,但大體是這樣一個流程。
希望能在查看這個流程中得到意外的收獲~
當(dāng)然文中不可避免可能會出現(xiàn)錯誤诵闭,我的一些理解可能不足夠準(zhǔn)確炼团,希望不會對看到文章的同學(xué)造成困擾。
參考鏈接:
Android 源碼,
Zygote.java
,ActivityManagerServer.java
,Process
,ZygoteInit.java
,ActivityThread.java
,LoadedApk.java
,ApplicationLoaders.java