Android Small外掛化框架解讀——Activity註冊和生命週期[阿里工程師分享]
通過對嵌入式企鵝圈原創團隊成員degao之前發表的《Android Small外掛化框架原始碼分析》的學習,對Android使用的外掛化技術有了初步的瞭解,但還是有很多需要認真學習的地方,特別是大部分知識都需要結合虛擬機器和Androidframwork的原理才能慢慢理解。比如,文中作者提到了外掛化框架要解決的三個核心問題:
1)外掛類的載入;
2)外掛資源的處理;
3)Activity註冊和生命週期問題;
其中第3點作者是這樣解釋的,“大部分外掛化框架解決辦法都是採用在宿主工程裡預先註冊Activity佔坑,然後通過佔坑Activity將生命週期回撥傳回給外掛Activity的方式。這裡Small處理的比較有特色,通過替換 ActivityThread 裡的mInstrumentation,在Instrumentation的newActivty實現裡面例項化了外掛Activity,通過較小改動就能完全解決生命週期回撥的問題。”。
這裡面提到的ActivityThread、Instrumentation、newActivity是什麼東西呢?該如何理解“預先註冊Activity佔坑”、“替換 ActivityThread 裡的mInstrumentation”這些步驟呢?帶著這些問題,我學習了Android framework中啟動Activity的原始碼,基本上搞懂了Small外掛化框架是如何處理和控制Activity生命週期的了,在這裡總結一下這個學習過程。
我們App中Activity的生命週期由ActivityManagerService來管理,它決定著什麼時候該呼叫onCreate、onResume這些生命週期的回撥函式,把Activity放在哪個棧裡,上下文之間的關係是怎樣的等等。當我們把一個外掛apk用classLoader載入進宿主應用後,外掛apk裡的Activity並不能被ActivityManagerService所管理,因為它只是作為一般的class被載入到宿主的程序空間中,而沒有在AndroidManifest.xml中宣告,因此通過startActivity啟動外掛裡的activity時會丟擲異常:
“android.content.ActivityNotFoundException: Unable to find explicit activity class… … … …
have you declared this activity in your AndroidManifest.xml?”
顯然,宿主應用自己來管理外掛中activity的生命週期有很大難度,最好還是把這項工作交給ActivityManagerService。可以預先在AndroidManifest.xml中定義一些空的Activity,給它們預先起上特定的名字(ProxyActivty$1),當要啟動外掛裡的activity(PluginActivity1)時,Small框架就先啟動ProxyActivty$1,這樣它的生命週期就會交由AMS去管理了,在真正構造和載入Activity的地方,再用PluginActivity1這個類的例項去替換ProxyActivty$1的例項。這樣一來,AMS中管理的是ProxyActivty$1這個activity,但實際上在宿主應用程序中例項化的卻是外掛裡的PluginActivity1,它裡面的onCreate、onResume等函式會被依次呼叫,生命週期等問題就交由AMS去控制了,相當於欺騙了AMS和系統。
這就是解決Activity註冊和生命週期問題的方法。要理解這個過程,需要分三步走:
1)App所在的客戶端程序和AMS所在的system_server程序之間是如何互動的;
2)搞清啟動一個Activity的前前後後,特別是從客戶端程序進入到AMS的入口,和從AMS返回到客戶端程序的入口,從中找到activity是怎樣被交給AMS的,activity是在哪兒例項化的;
3)要關注上面這個過程中具體涉及到的類和方法,從中選擇合適的Hook點,用Java反射的方法來進行動態替換,自己實現偷樑換柱的動作。
一、App和AMS通過Binder互動
1. AMS的初始化
ActivityManagerService執行在system_server程序中,在SystemServer啟動後完成初始化工作,然後加入到ServiceManager中。ServiceManager管理所有的Android系統服務,客戶端應用如果要使用系統服務,呼叫getSystemService介面,ServiceManager就會返回給客戶端應用對應的系統服務的IBinder物件。
在SystemServer的run函式中會呼叫startBootstrapServices先啟動AMS、PMS這樣關鍵的系統服務,它裡面會初始化AMS。先調SystemServiceManager的startService方法,注意傳入的引數不是ActivityManagerService.class,而是它的一個靜態內部類Liftcycle。
startService會呼叫同名的函式,用反射構造出傳入class的例項,也就是ActivityManagerService.Liftcycle的例項並返回。
在startBootstrapServices最後會調setSystemProcess把AMS加到ServiceManager中。
2. AMS在服務端的代理ActivityManagerNative
Android Binder機制在客戶端和服務端各有一個代理Proxy和Stub,它們之間通過transact和onTransact進行程序間通訊,AMS在服務端的代理是ActivityManagerNative,它和客戶端的代理ActivityManagerProxy進行Binder通訊,呼叫AMS提供的各種功能。AMS對外提供的介面是IActivityManager,它裡面包含了AMS對外提供服務的全部函式,有startActivity、startService、registerReceiver等等,ActivityManagerNative實現了IActivityManager,但它只是一個抽象類,只用於和客戶端的Binder通訊,startActivity等的具體實現在它的子類ActivityManagerService中完成。
3.AMS在客戶端的代理ActivityManagerProxy
應用App使用AMS提供的功能,比如startActivity,是通過AMS在客戶端的代理ActivityManagerProxy發起的。這裡ActivityManagerNative的getDefault方法返回就是ActivityManagerProxy的例項。
getDefault返回的是ActivityManagerNative中的一個單例gDefault,它通過ServiceManager獲取到AMS的IBinder,然後再通過asInterface得到ActivityManagerProxy。
ActivityManagerNative的asInterface方法在客戶端和服務端都會被呼叫。在客戶端,因為IBinder在native的實現BpInterface沒有過載queryLocalInterface,所以返回它的預設實現,預設為null,走new ActivityManagerProxy的分支。可見,ActivityManagerProxy的asBinder返回的就是從ServiceManager中取到的AMS的IBinder物件。
4. ActivityThread在客戶端的代理ApplicationThreadNative
與服務端相似,App在客戶端程序中完成例項化Activity、呼叫onCreate等生命週期函式的功能,也不能被AMS直接呼叫,而是通過自己在客戶端的代理ApplicationThreadNative來處理。雖然代理的名字有Thread字樣,但它並不表示App所在的程序,ActivityThread才是描述客戶端程序的類。也就是說當新建立一個應用程序時,系統就會為我們新構造一個ActivityThread物件。IApplicationThread是個介面,裡面有scheduleLaunchActivity、scheduleCreateService等需要在客戶端程序中呼叫的方法。ApplicationThreadNative是個Binder,負責與它在服務端的代理ApplicationThreadProxy通訊,同時也實現了IApplicationThread介面,但具體的實現放在了它的子類ApplicationThread中。
ApplicationThread物件被客戶端應用程序ActivityThread所持有,ActivityThread與AMS的互動實際上遵循了設計模式中的代理模式:
üIApplicationThread是抽象物件角色,提供了要使用的業務邏輯的抽象介面。
üActivityThread是目標物件角色,AMS不能直接與它互動、直接使用它的功能,都是通過它的代理類來進行。
üApplicationThread是代理物件角色,是ActivityThread的代理類,實現了具體的業務邏輯。與一般的代理模式不同,它不是直接持有ActivityThead的一個引用,而是把處理的請求發到ActivityThread內部的一個Handler上。
5.ActivityThread在服務端的代理ApplicationThreadProxy
與獲取ActivityManagerNative在客戶端的代理ActivityManagerProxy的過程類似,ApplicationThreadNative在服務端,也就是AMS所在的system_server程序中的代理,也是通過呼叫一個叫asInterface的函式來獲得的。
可見返回的ApplicationThreadProxy物件是IApplicationThread這個介面的型別,在ActivityManagerNative.java的onTransact中會用到這個客戶端的代理,比如在App中呼叫startActivity時,ActivityManagerNative在服務端的onTransact函式裡會呼叫AMS的startActivity,這時就通過ApplicationThreadNative.asInterface得到ApplicationThreadProxy,把它作為startActivity的引數傳給AMS。ApplicationThreadProxy在這兒的用處是作為app的代理,代表這客戶端的ActivityThread,因為AMS要處理多個客戶端程序的請求,所以通過這個代理可以得到客戶端的pid、uid、ProcessRecord等資訊,還會通過它發起對客戶端的Binder呼叫。
6.小結
Android的命名可以幫助我們區分這些類。ActivityThread代表了應用所在的程序,xxxNative是在本程序內的Binder代理類,xxxProxy是在對方程序的Binder代理類,熟悉Binder機制的話可以想到Binder在C++層的實現也是這樣命名BnXXX和BpXXX的。
二、startActivity的過程
1.從App進入AMS的過程
呼叫startActivity有兩種情況,一是Context(實際上是它的具體實現ContextImpl)調startActivity,一是Activity調startActivity。最終它們都會呼叫Instrumentation的execStartActivity。圖15是ContextImpl呼叫的程式碼,mMainThread就是ActivityThread,Instrumentation是它裡面的一個成員。圖16是Activity呼叫的程式碼,startActivity先呼叫了startActivityForResult,在startActivityForResult中再呼叫了Instrumentation的execStartActivity。注意,在Context中呼叫startActivity時,因為上下文中沒有Activity的棧,所以要加上FLAG_ACTIVITY_NEW_TASK這個flag。Instrumentation是工具、插樁的意思,顧名思義Activity只是在執行時經過Instrumentation,以便它起到監控和測試的功能。
execStartActivity中通過呼叫ActivityManagerNative.getDefault()獲得AMS在客戶端的代理物件ActivityManagerProxy,它是ActivityManagerNative類中的一個靜態單例物件。然後呼叫它的startActivity方法,實際上是通過binder的transact發出命令,由AMS客戶端的代理ActivityManagerNative的onTransact來處理,
App請求AMS的其他函式呼叫過程與此相似,無一例外的都是經過ActivityManagerNative裡面的單例物件gDefault,它是從客戶端程序發起呼叫進入AMS的中轉站,這為我們提供了一個hook點,按照一開始所設想的那樣,原來startActivity啟動的是外掛中的PluginActivity1,在這裡可以hook gDefault,這樣所有客戶端向AMS發起的呼叫,比如startService之類的都可以被我們截獲,然後就可以修改其中的引數,替換成含有AndroidManifest.xml中預先佔坑的ProxyActivity$1的Intent了。對宿主應用來說,它啟動的是PluginActivity1,對AMS來說,它管理的activity是ProxyActivity$1。
2.在AMS中處理的過程
AMS中處理startActivity的過程比較複雜,主要涉及了ActivityManagerService、ActivityStackSupervisor、ActivityStack、PackageManagerService、WindowManagerService等幾個類。
在ActivityManagerService中,startActivity先是呼叫了startActivityAsUser,然後在startActivityAsUser中呼叫了ActivityStackSupervisor的startActivityMayWait。注意,第一個引數caller就是我們前面說過的app在服務端的代理ApplicationThreadProxy,它是一個Binder物件,實現了IApplicationThread。轉到ActivityStackSupervisor中後,又在它和ActivityStack之間來回呼叫了許多次,主要是進行棧和Task的管理、解析Activity的資訊,準備視窗的切換之類的工作,最後回到了ActivityStackSupervisor中,呼叫realStartActivityLocked函式。
在realStartActivityLocked函式中,app是ProcessRecord型別,app.thread是IApplicationThread型別,也就是從客戶端的代理ApplicationThreadProxy,在這兒呼叫了它的scheduleLaunchActivity方法,接下來就會回到app的程序空間裡。
我們無法在AMS中去hook類和方法,因為它在system_server程序中,我們沒辦法跨越程序的鴻溝,當然也沒有許可權注入到system_server程序中。而且,AMS中的處理流程非常複雜,都是棧的管理之類的邏輯,很難找到hook點,來把佔坑的Activity替換回外掛裡的activity,所以我們還是得回到app程序中。
3.重新回到app程序
前面說過在ApplicationThreadNative中會處理AMS對ActivityThread的binder呼叫,它的onTransact函式會呼叫scheduleLaunchActivity,其具體實現在ApplicationThread中。ApplicationThread是ActivityThread裡的一個內部類,它的scheduleLaunchActivity的實現就是發一個LAUNCH_ACTIVITY型別的message到ActivityThread中的一個handler上。
這個名為H的handler中用handleLaunchActivity函式來處理AMS發起的scheduleLaunchActivity呼叫。handleLaunchActivity裡又呼叫了performLaunchActivity。
performLaunchActivity中又用到了Instrumentation類,調它的newActivity函式構造出activity物件。newActivity函式很簡單,直接用classLoader載入了Activity類,然後用反射調它的建構函式newInstance出activity例項。
然後再回到performLaunchActivity中,在通過netActivity得到activity的例項後,接下來就該呼叫它的生命週期函式onCreate了,照舊還是通過Instrumentation來完成,呼叫它的callActivityOnCreate函式。
在callActivityOnCreate裡會呼叫Activity的performCreate函式,它裡面呼叫的就是我們熟悉的onCreate了。
到此為止,我們簡單地跟蹤了startActivity的整個流程,根據前面的設想,這裡的Activity還是佔坑的ProxyActivity$1,要是能把它替換回外掛的PluginActivity1就好了。從前面的流程可以看出,Instrumentation這個類是startActivity整個過程中的必經之路,無論是從app到AMS,還是從AMS回到app都會經過它,要是能hook它就好了,因為它是ActivityThread裡的一個成員mInstrumentation,所以我們在客戶端程序中可以通過反射拿到ActivityThread物件,也可以拿到mInstrumentation。
三、利用反射完成動態代理
前面我們梳理了startActivity的整個呼叫流程,發現在app和AMS之間來回呼叫的過程中有兩個可以hook的點,下面我們來具體分析一下:
1)從app程序進入到AMS中,都是通過AMS在客戶端程序的代理ActivityManagerNative類裡的靜態單例物件gDefault來發起binder呼叫的,它是ActivityManagerProxy型別。我們可以在這個地方換掉startActivity傳入的引數,把佔坑的ProxyActivity$1傳給AMS。但是從AMS返回到app裡,呼叫scheduleLaunchActivity等一系列動作卻不會經過它,而是經過客戶端的代理ApplicationThreadNative發起對客戶端的binder呼叫,但如果hook ApplicationThreadNative也不能解決問題,因為最後真正構造出activity例項的步驟是在Instrumentation的newActivity函式中,ApplicationThreadNative物件離著它還很遠。
2)參考第一部分我們分析的流程,從app進入AMS和從AMS返回到app,在客戶端程序中都要經過Instrumentation這個類。從app進入AMS時,呼叫它的execStartActivity函式,在execStartActivity中再調ActivityManagerNative.getDefault().startActivity,發起binder呼叫。從AMS返回app時,呼叫它的newActivity,用發射構造出activity的例項,所以Instrumentation才是我們需要hook的類,可以在進入AMS時把外掛的PluginActivity1換成佔坑的ProxyActivity$1,在回到app構造真正的activity時,new出外掛的PluginActivity1。這樣一來,對AMS來說它管理的是ProxyActivity$1,對宿主應用來說,外掛PluginActivity1在它的程序裡已經被例項化和運行了
現在我們來看Small框架中的程式碼是處理的。
ActivityThread中有一個單例物件sCurrentActivityThread,它是在attche時被賦值的。前面說過Instrumentation在ActivityThread中的例項是其成員mInstrumentation,所以我們先通過ActivityThread的靜態方法currentActivityThread得到它的例項sCurrentActivityThread,然後找到它的成員mInstrumentation,把它的引用物件換成自己實現的InstrumentationWrapper。InstrumentationWrapper也是Instrumentation型別,它裡面實現了execStartActivity和newActivity這兩個方法,用來替換掉原來的函式呼叫。
其中,execStartActivity函式在不同android版本中的引數有所不同,wrapIntent函式完成了替換Intent的動作,把PluginActivity1的Intent替換成了ProxyActivity$1的Intent。最後在ReflectAccelerator中完成原來函式的呼叫。
newActivity中的unwrapIntent是wrapIntent的反向工作,得到的className是外掛中的PluginActivity1的類名,最後再呼叫原來的newActivity完成activity的構造。
這樣最終完成了用PluginActivity1替換AndroidManifest.xml中的ProxyActivity$1的偷天換日的工作,外掛裡的PluginActivity1既可以被正常的載入執行也不用擔心它的生命週期之類的管理了。
- 嵌入式企鵝圈原創團隊由阿里、魅族、nvidia、龍芯、炬力、拓爾思等資深工程師組成。百分百原創,每週兩篇,分享嵌入式、Linux、物聯網、GPU、Android、自動駕駛等技術。歡迎掃碼關注微信公眾號:嵌入式企鵝圈,