轉(zhuǎn)自 http://weishu.me/2016/03/21/understand-plugin-framework-activity-management/
之前的Android插件化原理解析系列文章揭開了Hook機制的神秘面紗,現(xiàn)在我們手握倚天屠龍,那么如何通過這種技術完成插件化方案呢?具體來說妻献,插件中的Activity,Service等組件如何在Android系統(tǒng)上運行起來雄人?
在Java平臺要做到動態(tài)運行模塊傅联、熱插拔可以使用ClassLoader技術進行動態(tài)類加載撒妈,比如廣泛使用的OSGi技術。在Android上當然也可以使用動態(tài)加載技術运褪,但是僅僅把類加載進來就足夠了嗎难述?Activity,Service等組件是有生命周期的吐句,它們統(tǒng)一由系統(tǒng)服務AMS管理胁后;使用ClassLoader可以從插件中創(chuàng)建Activity對象,但是嗦枢,一個沒有生命周期的Activity對象有什么用攀芯?所以在Android系統(tǒng)上,僅僅完成動態(tài)類加載是不夠的文虏;我們需要想辦法把我們加載進來的Activity等組件交給系統(tǒng)管理侣诺,讓AMS賦予組件生命周期;這樣才算是一個有血有肉的完善的插件化方案氧秘。
接下來的系列文章會講述 DroidPlugin對于Android四大組件的處理方式年鸳,我們且看它如何采用Hook技術坑蒙拐騙把系統(tǒng)玩弄于股掌之中,最終賦予Activity丸相,Service等組件生命周期搔确,完成借尸還魂的。
首先灭忠,我們來看看DroidPlugin對于Activity組件的處理方式膳算。
閱讀本文之前,可以先clone一份understand-plugin-framework弛作,參考此項目的intercept-activity模塊涕蜂。另外,如果對于Hook技術不甚了解映琳,請先查閱我之前的文章:
AndroidManifest.xml的限制
讀到這里机隙,或許有部分讀者覺得疑惑了,啟動Activity不就是一個startActivity的事嗎萨西,有這么神秘兮兮的有鹿?
啟動Activity確實非常簡單,但是Android卻有一個限制:必須在AndroidManifest.xml中顯示聲明使用的Activity原杂;我相信讀者肯定會遇到下面這種異常:
03-1815:29:56.07420709-20709/com.weishu.intercept_activity.app E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.weishu.intercept_activity.app, PID:20709
android.content.ActivityNotFoundException: Unable to find explicit activityclass{com.weishu.intercept_activity.app/com.weishu.intercept_activity.app.TargetActivity}; have you declaredthisactivity in your AndroidManifest.xml?
『必須在AndroidManifest.xml中顯示聲明使用的Activity』這個硬性要求很大程度上限制了插件系統(tǒng)的發(fā)揮:假設我們需要啟動一個插件的Activity印颤,插件使用的Activity是無法預知的,這樣肯定也不會在Manifest文件中聲明穿肄;如果插件新添加一個Activity年局,主程序的AndroidManifest.xml就需要更新际看;既然雙方都需要修改升級,何必要使用插件呢矢否?這已經(jīng)違背了動態(tài)加載的初衷:不修改插件框架而動態(tài)擴展功能仲闽。
能不能想辦法繞過這個限制呢?
束手無策啊僵朗,怎么辦赖欣?借刀殺人偷梁換柱無中生有以逸待勞乘火打劫瞞天過海…等等验庙!偷梁換柱瞞天過海顶吮?貌似可以一試。
我們可以耍個障眼法:既然AndroidManifest文件中必須聲明粪薛,那么我就聲明一個(或者有限個)替身Activity好了悴了,當需要啟動插件的某個Activity的時候,先讓系統(tǒng)以為啟動的是AndroidManifest中聲明的那個替身违寿,暫時騙過系統(tǒng)湃交;然后到合適的時候又替換回我們需要啟動的真正的Activity;所謂瞞天過海藤巢,莫過如此搞莺!
現(xiàn)在有了方案了,但是該如何做呢掂咒?兵書又說才沧,知己知彼百戰(zhàn)不殆!如果連Activity的啟動過程都不熟悉俏扩,怎么完成這個瞞天過海的過程糜工?
Activity啟動過程
啟動Activity非常簡單弊添,一個startActivity就完事了录淡;那么在這個簡單調(diào)用的背后發(fā)生了什么呢?Look the fucking source code油坝!
關于Activity 的啟動過程嫉戚,也不是三言兩語能解釋清楚的,如果按照源碼一步一步走下來澈圈,插件化系列文章就不用寫了彬檀;所以這里我就給出一個大致流程,只列出關鍵的調(diào)用點(以Android 6.0源碼為例)瞬女;如果讀者希望更詳細的講解窍帝,可以參考老羅的Android應用程序的Activity啟動過程簡要介紹和學習計劃
首先是Activity類的startActivity方法:
1
2
3
publicvoidstartActivity(Intent intent){
startActivity(intent,null);
}
跟著這個方法一步一步跟蹤,會發(fā)現(xiàn)它最后在startActivityForResult里面調(diào)用了Instrument對象的execStartActivity方法诽偷;接著在這個函數(shù)里面調(diào)用了ActivityManagerNative類的startActivity方法坤学;這個過程在前文已經(jīng)反復舉例講解了疯坤,我們知道接下來會通過Binder IPC到AMS所在進程調(diào)用AMS的startActivity方法;Android系統(tǒng)的組件生命周期管理就是在AMS里面完成的深浮,那么在AMS里面到底做了什么呢压怠?
ActivityManagerService的startActivity方法如下:
1
2
3
4
5
6
7
8
publicfinalintstartActivity(IApplicationThread caller,StringcallingPackage,
Intent intent,StringresolvedType, IBinder resultTo,
StringresultWho,intrequestCode,intstartFlags,
StringprofileFile, ParcelFileDescriptor profileFd, Bundle options) {
returnstartActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode,
startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId());
}
很簡單,直接調(diào)用了startActivityAsUser這個方法飞苇;接著是ActivityStackSupervisor類的startActivityMayWait方法菌瘫。這個ActivityStackSupervisor類到底是個啥?如果仔細查閱布卡,低版本的Android源碼上是沒有這個類的雨让;后來AMS的代碼進行了部分重構,關于Activity棧管理的部分單獨提取出來成為了ActivityStackSupervisor類忿等;好了宫患,繼續(xù)看代碼。
startActivityMayWait這個方法前面對參數(shù)進行了一系列處理这弧,我們需要知道的是娃闲,在這個方法內(nèi)部對傳進來的Intent進行了解析,并嘗試從中取出關于啟動Activity的信息匾浪。
然后這個方法調(diào)用了startActivityLocked方法皇帮;在startActivityLocked方法內(nèi)部進行了一系列重要的檢查:比如權限檢查,Activity的exported屬性檢查等等蛋辈;我們上文所述的属拾,啟動沒有在Manifestfest中顯示聲明的Activity拋異常也是這里發(fā)生的:
1
2
3
4
5
if(err== ActivityManager.START_SUCCESS && aInfo == null) {
// We couldn't find the specific class specified in the Intent.
// Also the end of the line.
err= ActivityManager.START_CLASS_NOT_FOUND;
}
這里返回ActivityManager.START_CLASS_NOT_FOUND之后,在Instrument的execStartActivity返回之后會檢查這個值冷溶,然后跑出異常:
1
2
3
4
5
6
caseActivityManager.START_CLASS_NOT_FOUND:
if(intentinstanceofIntent && ((Intent)intent).getComponent() !=null)
thrownewActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+"; have you declared this activity in your AndroidManifest.xml?");
源碼看到這里渐白,我們已經(jīng)確認了『必須在AndroidManifest.xml中顯示聲明使用的Activity』的原因;然而這個校檢過程發(fā)生在AMS所在的進程system_server逞频,我們沒有辦法篡改纯衍,只能另尋他路。
OK苗胀,我們繼續(xù)跟蹤源碼襟诸;在startActivityLocked之后處理的都是Activity任務棧相關內(nèi)容,這一系列ActivityStack和ActivityStackSupervisor糾纏不清的調(diào)用看下圖就明白了基协;不明白也沒關系: D 目前用處不大歌亲。
這一系列調(diào)用最終到達了ActivityStackSupervisor的realStartActivityLocked方法;人如其名澜驮,這個方法開始了真正的“啟動Activity”:它調(diào)用了ApplicationThread的scheduleLaunchActivity方法陷揪,開始了真正的Activity對象創(chuàng)建以及啟動過程。
這個ApplicationThread是什么,是一個線程嗎悍缠?與ActivityThread有什么區(qū)別和聯(lián)系揩慕?
不要被名字迷惑了,這個ApplicationThread實際上是一個Binder對象扮休,是App所在的進程與AMS所在進程system_server通信的橋梁迎卤;在Activity啟動的過程中,App進程會頻繁地與AMS進程進行通信:
App進程會委托AMS進程完成Activity生命周期的管理以及任務棧的管理玷坠;這個通信過程AMS是Server端蜗搔,App進程通過持有AMS的client代理ActivityManagerNative完成通信過程;
AMS進程完成生命周期管理以及任務棧管理后八堡,會把控制權交給App進程樟凄,讓App進程完成Activity類對象的創(chuàng)建,以及生命周期回調(diào)兄渺;這個通信過程也是通過Binder完成的缝龄,App所在server端的Binder對象存在于ActivityThread的內(nèi)部類ApplicationThread;AMS所在client通過持有IApplicationThread的代理對象完成對于App進程的通信挂谍。
App進程與AMS進程的通信過程如圖所示:
App進程內(nèi)部的ApplicationThread server端內(nèi)部有自己的Binder線程池叔壤,它與App主線程的通信通過Handler完成,這個Handler存在于ActivityThread類口叙,它的名字很簡單就叫H炼绘,這一點我們接下來就會講到。
現(xiàn)在我們明白了這個ApplicationThread到底是個什么東西妄田,接上文繼續(xù)跟蹤Activity的啟動過程俺亮;我們查看ApplicationThread的scheduleLaunchActivity方法,這個方法很簡單疟呐,就是包裝了參數(shù)最終使用Handler發(fā)了一個消息脚曾。
正如剛剛所說,ApplicationThread所在的Binder服務端使用Handler與主線程進行通信启具,這里的scheduleLaunchActivity方法直接把啟動Activity的任務通過一個消息轉(zhuǎn)發(fā)給了主線程本讥;我們查看Handler類對于這個消息的處理:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
可以看到,這里直接調(diào)用了ActivityThread的handleLaunchActivity方法富纸,在這個方法內(nèi)部有一句非常重要:
1
Activity a = performLaunchActivity(r, customIntent);
繞了這么多彎囤踩,我們的Activity終于被創(chuàng)建出來了!繼續(xù)跟蹤這個performLaunchActivity方法看看發(fā)生了什么晓褪;由于這個方法較長,我就不貼代碼了综慎,讀者可以自行查閱涣仿;要指出的是,這個方法做了兩件很重要的事情:
使用ClassLoader加載并通過反射創(chuàng)建Activity對象
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
如果Application還沒有創(chuàng)建,那么創(chuàng)建Application對象并回調(diào)相應的生命周期方法好港;
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// ... 省略
if(r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
}else{
mInstrumentation.callActivityOnCreate(activity, r.state);
}
Activity的啟動過程到這里就結(jié)束了愉镰,可能讀者還是覺得迷惑:不就是調(diào)用了一系列方法嗎?具體做了什么還是不太清楚钧汹,而且為什么Android要這么設計丈探?
方法調(diào)用鏈再長也木有關系,有兩點需要明白:
平時我們所說的Application被創(chuàng)建了拔莱,onCreate方法被調(diào)用了碗降,我們或許并沒有意識到我們所說的Application, Activity除了代表Android應用層通常所代表的“組件”之外,它們其實都是普通的Java對象塘秦,也是需要被構造函數(shù)構造出來的對象的讼渊;在這個過程中,我們明白了這些對象到底是如何被創(chuàng)建的尊剔。
為什么需要一直與AMS進行通信爪幻?哪些操作是在AMS中進行的?其實AMS正如名字所說须误,管理所有的“活動”挨稿,整個系統(tǒng)的Activity堆棧,Activity生命周期回調(diào)都是由AMS所在的系統(tǒng)進程system_server幫開發(fā)者完成的京痢;Android的Framework層幫忙完成了諸如生命周期管理等繁瑣復雜的過程叶组,簡化了應用層的開發(fā)。
瞞天過豪欤——啟動不在AndroidManifest.xml中聲明的Activity
簡要分析
通過上文的分析甩十,我們已經(jīng)對Activity的啟動過程了如指掌了;就讓我們干點壞事吧 :D
對與『必須在AndroidManifest.xml中顯示聲明使用的Activity』這個問題吭产,上文給出了思路——瞞天過海侣监;我們可以在AndroidManifest.xml里面聲明一個替身Activity,然后在合適的時候把這個假的替換成我們真正需要啟動的Activity就OK了臣淤。
那么問題來了橄霉,『合適的時候』到底是什么時候?在前文Hook機制之動態(tài)代理中我們提到過Hook過程最重要的一步是尋找Hook點邑蒋;如果是在同一個進程姓蜂,startActivity到Activity真正啟動起來這么長的調(diào)用鏈,我們隨便找個地方Hook掉就完事兒了医吊;但是問題木有這么簡單钱慢。
Activity啟動過程中很多重要的操作(正如上文分析的『必須在AndroidManifest.xml中顯式聲明要啟動的Activity』)都不是在App進程里面執(zhí)行的,而是在AMS所在的系統(tǒng)進程system_server完成卿堂,由于進程隔離的存在束莫,我們對別的進程無能為力懒棉;所以這個Hook點就需要花點心思了。
這時候Activity啟動過程的知識就派上用場了览绿;雖然整個啟動過程非常復雜策严,但其實一張圖就能總結(jié):
先從App進程調(diào)用startActivity;然后通過IPC調(diào)用進入系統(tǒng)進程system_server饿敲,完成Activity管理以及一些校檢工作妻导,最后又回到了APP進程完成真正的Activioty對象創(chuàng)建。
由于這個檢驗過程是在AMS進程完成的怀各,我們對system_server進程里面的操作無能為力倔韭,只有在我們APP進程里面執(zhí)行的過程才是有可能被Hook掉的,也就是第一步和第三步渠啤;具體應該怎么辦呢狐肢?
既然需要一個顯式聲明的Activity,那就聲明一個沥曹!可以在第一步假裝啟動一個已經(jīng)在AndroidManifest.xml里面聲明過的替身Activity份名,讓這個Activity進入AMS進程接受檢驗;最后在第三步的時候換成我們真正需要啟動的Activity妓美;這樣就成功欺騙了AMS進程僵腺,瞞天過海!
說到這里壶栋,是不是有點小激動呢辰如?我們寫個demo驗證一下:『啟動一個并沒有在AndroidManifest.xml中顯示聲明的Activity』
實戰(zhàn)過程
具體來說,我們打算實現(xiàn)如下功能:在MainActivity中啟動一個并沒有在AndroidManifest.xml中聲明的TargetActivity贵试;按照上文分析琉兜,我們需要聲明一個替身Activity,我們叫它StubActivity毙玻;
那么豌蟋,我們的AndroidManifest.xml如下:
package="com.weishu.intercept_activity.app">
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
>
OK,那么我們啟動TargetActivity很簡單桑滩,就是個startActivity調(diào)用的事:
1
startActivity(newIntent(MainActivity.this, TargetActivity.class));
如果你直接這么運行梧疲,肯定會直接拋出ActivityNotFoundException然后直接退出;我們接下來要做的就是讓這個調(diào)用成功啟動TargetActivity运准。
貍貓換太子——使用替身Activity繞過AMS
由于AMS進程會對Activity做顯式聲明驗證幌氮,因此在
啟動Activity的控制權轉(zhuǎn)移到AMS進程之前,我們需要想辦法臨時把TargetActivity替換成替身StubActivity胁澳;在這之間有很長的一段調(diào)用鏈该互,我們可以輕松Hook掉;選擇什么地方Hook是一個很自由的事情听哭,但是Hook的步驟越后越可靠——Hook得越早慢洋,后面的調(diào)用就越復雜塘雳,越容易出錯陆盘。
我們可以選擇在進入AMS進程的入口進行Hook普筹,具體來說也就是HookAMS在本進程的代理對象ActivityManagerNative。如果你不知道如何Hook掉這個AMS的代理對象隘马,請查閱我之前的文章Hook機制之AMS&PMS
我們Hook掉ActivityManagerNative對于startActivity方法的調(diào)用太防,替換掉交給AMS的intent對象,將里面的TargetActivity的暫時替換成已經(jīng)聲明好的替身StubActivity酸员;這種Hook方式前文講述的很詳細蜒车,不贅述;替換的關鍵代碼如下:
if("startActivity".equals(method.getName())) {
// 只攔截這個方法
// 替換參數(shù), 任你所為;甚至替換原始Activity啟動別的Activity偷梁換柱
// API 23:
// public final Activity startActivityNow(Activity parent, String id,
// Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
// Activity.NonConfigurationInstances lastNonConfigurationInstances) {
// 找到參數(shù)里面的第一個Intent 對象
Intent raw;
intindex =0;
for(inti =0; i < args.length; i++) {
if(args[i]instanceofIntent) {
index = i;
break;
}
}
raw = (Intent) args[index];
Intent newIntent =newIntent();
// 這里包名直接寫死,如果再插件里,不同的插件有不同的包? 傳遞插件的包名即可
String targetPackage ="com.weishu.intercept_activity.app";
// 這里我們把啟動的Activity臨時替換為 StubActivity
ComponentName componentName =newComponentName(targetPackage, StubActivity.class.getCanonicalName());
newIntent.setComponent(componentName);
// 把我們原始要啟動的TargetActivity先存起來
newIntent.putExtra(HookHelper.EXTRA_TARGET_INTENT, raw);
// 替換掉Intent, 達到欺騙AMS的目的
args[index] = newIntent;
Log.d(TAG,"hook success");
returnmethod.invoke(mBase, args);
}
returnmethod.invoke(mBase, args);
通過這個替換過程幔嗦,在ActivityManagerNative的startActivity調(diào)用之后酿愧,system_server端收到Binder驅(qū)動的消息,開始執(zhí)行ActivityManagerService里面真正的startActivity方法邀泉;這時候AMS看到的intent參數(shù)里面的組件已經(jīng)是StubActivity了嬉挡,因此可以成功繞過檢查,這時候如果不做后面的Hook汇恤,直接調(diào)用
1
startActivity(newIntent(MainActivity.this, TargetActivity.class));
也不會出現(xiàn)上文的ActivityNotFoundException
借尸還魂——攔截Callback從恢復真身
行百里者半九十∨痈郑現(xiàn)在我們的startActivity啟動一個沒有顯式聲明的Activity已經(jīng)不會拋異常了,但是要真正正確地把TargetActivity啟動起來因谎,還有一些事情要做基括。其中最重要的一點是,我們用替身StubActivity臨時換了TargetActivity财岔,肯定需要在『合適的』時候替換回來风皿;接下來我們就完成這個過程。
在AMS進程里面我們是沒有辦法換回來的匠璧,因此我們要等AMS把控制權交給App所在進程桐款,也就是上面那個『Activity啟動過程簡圖』的第三步。AMS進程轉(zhuǎn)移到App進程也是通過Binder調(diào)用完成的患朱,承載這個功能的Binder對象是IApplicationThread鲁僚;在App進程它是Server端,在Server端接受Binder遠程調(diào)用的是Binder線程池裁厅,Binder線程池通過Handler將消息轉(zhuǎn)發(fā)給App的主線程冰沙;(我這里不厭其煩地敘述Binder調(diào)用過程,希望讀者不要反感执虹,其一加深印象拓挥,其二懂Binder真的很重要)我們可以在這個Handler里面將替身恢復成真身。
這里不打算講述Handler 的原理袋励,我們簡單看一下Handler是如何處理接收到的Message的侥啤,如果我們能攔截這個Message的接收過程当叭,就有可能完成替身恢復工作;Handler類的dispathMesage如下:
publicvoiddispatchMessage(Message msg){
if(msg.callback !=null) {
handleCallback(msg);
}else{
if(mCallback !=null) {
if(mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
從這個方法可以看出來盖灸,Handler類消息分發(fā)的過程如下:
如果傳遞的Message本身就有callback蚁鳖,那么直接使用Message對象的callback方法;
如果Handler類的成員變量mCallback存在赁炎,那么首先執(zhí)行這個mCallback回調(diào)醉箕;
如果mCallback的回調(diào)返回true,那么表示消息已經(jīng)成功處理徙垫;直接結(jié)束讥裤。
如果mCallback的回調(diào)返回false,那么表示消息沒有處理完畢姻报,會繼續(xù)使用Handler類的handleMessage方法處理消息己英。
那么,ActivityThread中的Handler類H是如何實現(xiàn)的呢吴旋?H的部分源碼如下:
publicvoidhandleMessage(Message msg){
if(DEBUG_MESSAGES) Slog.v(TAG,">>> handling: "+ codeToString(msg.what));
switch(msg.what) {
caseLAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"activityStart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r,null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}break;
caseRELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// 以下略
}
}
可以看到H類僅僅重載了handleMessage方法损肛;通過dispathMessage的消息分發(fā)過程得知,我們可以攔截這一過程:把這個H類的mCallback替換為我們的自定義實現(xiàn)邮府,這樣dispathMessage就會首先使用這個自定義的mCallback荧关,然后看情況使用H重載的handleMessage。
這個Handler.Callback是一個接口褂傀,我們可以使用動態(tài)代理或者普通代理完成Hook忍啤,這里我們使用普通的靜態(tài)代理方式;創(chuàng)建一個自定義的Callback類:
/* package */classActivityThreadHandlerCallbackimplementsHandler.Callback{
Handler mBase;
publicActivityThreadHandlerCallback(Handler base){
mBase = base;
}
@Override
publicbooleanhandleMessage(Message msg){
switch(msg.what) {
// ActivityThread里面 "LAUNCH_ACTIVITY" 這個字段的值是100
// 本來使用反射的方式獲取最好, 這里為了簡便直接使用硬編碼
case100:
handleLaunchActivity(msg);
break;
}
mBase.handleMessage(msg);
returntrue;
}
privatevoidhandleLaunchActivity(Message msg){
// 這里簡單起見,直接取出TargetActivity;
Object obj = msg.obj;
// 根據(jù)源碼:
// 這個對象是 ActivityClientRecord 類型
// 我們修改它的intent字段為我們原來保存的即可.
/*? ? ? ? switch (msg.what) {
/? ? ? ? ? ? case LAUNCH_ACTIVITY: {
/? ? ? ? ? ? ? ? Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
/? ? ? ? ? ? ? ? final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
/
/? ? ? ? ? ? ? ? r.packageInfo = getPackageInfoNoCheck(
/? ? ? ? ? ? ? ? ? ? ? ? r.activityInfo.applicationInfo, r.compatInfo);
/? ? ? ? ? ? ? ? handleLaunchActivity(r, null);
*/
try{
// 把替身恢復成真身
Field intent = obj.getClass().getDeclaredField("intent");
intent.setAccessible(true);
Intent raw = (Intent) intent.get(obj);
Intent target = raw.getParcelableExtra(HookHelper.EXTRA_TARGET_INTENT);
raw.setComponent(target.getComponent());
}catch(NoSuchFieldException e) {
e.printStackTrace();
}catch(IllegalAccessException e) {
e.printStackTrace();
}
}
}
這個Callback類的使命很簡單:把替身StubActivity恢復成真身TargetActivity仙辟;有了這個自定義的Callback之后我們需要把ActivityThread里面處理消息的Handler類H的的mCallback修改為自定義callback類的對象:
// 先獲取到當前的ActivityThread對象
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
Object currentActivityThread = currentActivityThreadField.get(null);
// 由于ActivityThread一個進程只有一個,我們獲取這個對象的mH
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(currentActivityThread);
// 設置它的回調(diào), 根據(jù)源碼:
// 我們自己給他設置一個回調(diào),就會替代之前的回調(diào);
//? ? ? ? public void dispatchMessage(Message msg) {
//? ? ? ? ? ? if (msg.callback != null) {
//? ? ? ? ? ? ? ? handleCallback(msg);
//? ? ? ? ? ? } else {
//? ? ? ? ? ? ? ? if (mCallback != null) {
//? ? ? ? ? ? ? ? ? ? if (mCallback.handleMessage(msg)) {
//? ? ? ? ? ? ? ? ? ? ? ? return;
//? ? ? ? ? ? ? ? ? ? }
//? ? ? ? ? ? ? ? }
//? ? ? ? ? ? ? ? handleMessage(msg);
//? ? ? ? ? ? }
//? ? ? ? }
Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
mCallBackField.set(mH,newActivityThreadHandlerCallback(mH));
到這里同波,我們已經(jīng)成功地繞過AMS,完成了『啟動沒有在AndroidManifest.xml中顯式聲明的Activity』的過程叠国;瞞天過海未檩,這種玩弄系統(tǒng)與股掌之中的快感你們能體會到嗎?
僵尸or活人粟焊?——能正確收到生命周期回調(diào)嗎
雖然我們完成了『啟動沒有在AndroidManifest.xml中顯式聲明的Activity 』冤狡,但是啟動的TargetActivity是否有自己的生命周期呢,我們還需要額外的處理過程嗎项棠?
實際上TargetActivity已經(jīng)是一個有血有肉的Activity了:它具有自己正常的生命周期悲雳;可以運行Demo代碼驗證一下。
這個過程是如何完成的呢香追?我們以onDestroy為例簡要分析一下:
從Activity的finish方法開始跟蹤合瓢,最終會通過ActivityManagerNative到AMS然后接著通過ApplicationThread到ActivityThread,然后通過H轉(zhuǎn)發(fā)消息到ActivityThread的handleDestroyActivity透典,接著這個方法把任務交給performDestroyActivity完成晴楔。
在真正分析這個方法之前顿苇,需要說明一點的是:不知讀者是否感受得到,App進程與AMS交互幾乎都是這么一種模式税弃,幾個角色 ActivityManagerNative, ApplicationThread, ActivityThread以及Handler類H分工明確纪岁,讀者可以按照這幾個角色的功能分析AMS的任何調(diào)用過程,屢試不爽钙皮;這也是我的初衷——希望分析插件框架的過程中能幫助深入理解Android Framework蜂科。
好了繼續(xù)分析performDestroyActivity顽决,關鍵代碼如下:
ActivityClientRecord r = mActivities.get(token);
// ...略
mInstrumentation.callActivityOnDestroy(r.activity);
這里通過mActivities拿到了一個ActivityClientRecord短条,然后直接把這個record里面的Activity交給Instrument類完成了onDestroy的調(diào)用。
在我們這個demo的場景下才菠,r.activity是TargetActivity還是StubActivity茸时?按理說,由于我們欺騙了AMS赋访,AMS應該只知道StubActivity的存在可都,它壓根兒就不知道TargetActivity是什么,為什么它能正確完成對TargetActivity生命周期的回調(diào)呢蚓耽?
一切的秘密在token里面渠牲。AMS與ActivityThread之間對于Activity的生命周期的交互,并沒有直接使用Activity對象進行交互步悠,而是使用一個token來標識签杈,這個token是binder對象,因此可以方便地跨進程傳遞鼎兽。Activity里面有一個成員變量mToken代表的就是它答姥,token可以唯一地標識一個Activity對象,它在Activity的attach方法里面初始化谚咬;
在AMS處理Activity的任務棧的時候鹦付,使用這個token標記Activity,因此在我們的demo里面择卦,AMS進程里面的token對應的是StubActivity敲长,也就是AMS還在傻乎乎地操作StubActivity(關于這一點,你可以dump出任務棧的信息秉继,可以觀察到dump出的確實是StubActivity)祈噪。但是在我們App進程里面,token對應的卻是TargetActivity秕噪!因此钳降,在ActivityThread執(zhí)行回調(diào)的時候,能正確地回調(diào)到TargetActivity相應的方法腌巾。
為什么App進程里面遂填,token對應的是TargetActivity呢铲觉?
回到代碼蹬刷,ActivityClientRecord是在mActivities里面取出來的踱葛,確實是根據(jù)token冗湫Α矛紫;那么這個token是什么時候添加進去的呢舀凛?我們看performLaunchActivity就完成明白了:它通過classloader加載了TargetActivity恰画,然后完成一切操作之后把這個activity添加進了mActivities踪少!另外批什,在這個方法里面我們還能看到對Ativityattach方法的調(diào)用哆窿,它傳遞給了新創(chuàng)建的Activity一個token對象链烈,而這個token是在ActivityClientRecord構造函數(shù)里面初始化的。
至此我們已經(jīng)可以確認挚躯,通過這種方式啟動的Activity有它自己完整而獨立的生命周期强衡!
小節(jié)
本文講述了『啟動一個并沒有在AndroidManifest.xml中顯示聲明的Activity』的解決辦法,我們成功地繞過了Android的這個限制码荔,這個是插件Activity管理技術的基礎漩勤;但是要做到啟動一個插件Activity問題遠沒有這么簡單。
首先缩搅,在Android中越败,Activity有不同的啟動模式;我們聲明了一個替身StubActivity硼瓣,肯定沒有滿足所有的要求究飞;因此,我們需要在AndroidManifest.xml中聲明一系列的有不同launchMode的Activity巨双,還需要完成替身與真正Activity launchMode的匹配過程噪猾;這樣才能完成啟動各種類型Activity的需求,關于這一點筑累,在 DroidPlugin 的com.morgoo.droidplugin.stub包下面可以找到袱蜡。
另外,每啟動一個插件的Activity都需要一個StubActivity慢宗,但是AndroidManifest.xml中肯定只能聲明有限個坪蚁,如果一直startActivity而不finish的話,那么理論上就需要無限個StubActivity镜沽;這個問題該如何解決呢敏晤?事實上,這個問題在技術上沒有好的解決辦法缅茉。但是嘴脾,如果你的App startActivity了十幾次,而沒有finish任何一個Activity,這樣在Activity的回退棧里面有十幾個Activity译打,用戶難道按back十幾次回到主頁嗎耗拓?有這種需求說明你的產(chǎn)品設計有問題;一個App一級頁面奏司,二級頁面..到五六級的頁面已經(jīng)影響體驗了乔询,所以,每種LauchMode聲明十個StubActivity絕對能滿足需求了韵洋。
最后竿刁,在本文所述例子中,TargetActivity與StubActivity存在于同一個Apk搪缨,因此系統(tǒng)的ClassLoader能夠成功加載并創(chuàng)建TargetActivity的實例食拜。但是在實際的插件系統(tǒng)中,要啟動的目標Activity肯定存在于一個單獨的文件中勉吻,系統(tǒng)默認的ClassLoader無法加載插件中的Activity類——系統(tǒng)壓根兒就不知道要加載的插件在哪监婶,談何加載?因此還有一個很重要的問題需要處理:
我們要完成插件系統(tǒng)中類的加載齿桃,這可以通過自定義ClassLoader實現(xiàn)。解決了『啟動沒有在AndroidManifest.xml中顯式聲明的煮盼,并且存在于外部文件中的Activity』的問題短纵,插件系統(tǒng)對于Activity的管理才算得上是一個完全體。篇幅所限僵控,欲知后事如何香到,請聽下回分解!