Android App 啟動時的操作之 ClassLoader 和 Application 初始化
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
分析,在這裡沒有細說。需要的可以參考連結:
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 Login 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 Login 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<String, ClassLoader> mLoaders
; -
Application
的建立是利用ClassLoader
的loadclass()
實現的 -
Application
的attachBaseContext()
是早於onCreate()
的呼叫的 -
getApplicationContext()
不為null
是晚於attachBaseContext()
, 早於onCreate()
方法的
坑邊閒話
關於這部分的內容,有些枯燥,檢視原始碼的過程比較枯燥,這裡面的原始碼具體 api 是 26 還是多少,暫時不定,因為後面你如果自己看原始碼可能會發現 api 已經修改了,但大體是這樣一個流程。
希望能在檢視這個流程中得到意外的收穫~
當然文中不可避免可能會出現錯誤,我的一些理解可能不足夠準確,希望不會對看到文章的同學造成困擾。
參考連結:
-
Android 原始碼,
Zygote.java
,ActivityManagerServer.java
,Process
,ZygoteInit.java
,ActivityThread.java
,LoadedApk.java
,ApplicationLoaders.java