Android 插件化原理解析——Activity生命周期管理

轉(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技術不甚了解映琳,請先查閱我之前的文章:

Hook機制之動態(tài)代理

Hook機制之Binder Hook

Hook機制之AMS&PMS

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)用流程圖

這一系列調(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的管理才算得上是一個完全體。篇幅所限僵控,欲知后事如何香到,請聽下回分解!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末报破,一起剝皮案震驚了整個濱河市悠就,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌充易,老刑警劉巖梗脾,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異盹靴,居然都是意外死亡炸茧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門稿静,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梭冠,“玉大人,你說我怎么就攤上這事改备】啬” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵悬钳,是天一觀的道長盐捷。 經(jīng)常有香客問我柬脸,道長,這世上最難降的妖魔是什么毙驯? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任倒堕,我火速辦了婚禮,結(jié)果婚禮上爆价,老公的妹妹穿的比我還像新娘垦巴。我一直安慰自己,他們只是感情好铭段,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布骤宣。 她就那樣靜靜地躺著,像睡著了一般序愚。 火紅的嫁衣襯著肌膚如雪憔披。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天爸吮,我揣著相機與錄音芬膝,去河邊找鬼。 笑死形娇,一個胖子當著我的面吹牛锰霜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播桐早,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼癣缅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了哄酝?” 一聲冷哼從身側(cè)響起友存,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陶衅,沒想到半個月后屡立,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡万哪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年侠驯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奕巍。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡吟策,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出的止,到底是詐尸還是另有隱情檩坚,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站匾委,受9級特大地震影響拖叙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赂乐,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一薯鳍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挨措,春花似錦挖滤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至觉既,卻和暖如春惧盹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瞪讼。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工钧椰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尝艘。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓演侯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親背亥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容