之前的 Android插件化原理解析 系列文章揭開了Hook機(jī)制的神秘面紗颓芭,現(xiàn)在我們手握倚天屠龍,那么如何通過這種技術(shù)完成插件化方案呢柬赐?具體來說亡问,插件中的Activity,Service等組件如何在Android系統(tǒng)上運(yùn)行起來肛宋?
在Java平臺(tái)要做到動(dòng)態(tài)運(yùn)行模塊玛界、熱插拔可以使用ClassLoader
技術(shù)進(jìn)行動(dòng)態(tài)類加載,比如廣泛使用的OSGi
技術(shù)悼吱。在Android上當(dāng)然也可以使用動(dòng)態(tài)加載技術(shù),但是僅僅把類加載進(jìn)來就足夠了嗎良狈?Activity
后添,Service
等組件是有生命周期的,它們統(tǒng)一由系統(tǒng)服務(wù)AMS
管理薪丁;使用ClassLoader
可以從插件中創(chuàng)建Activity對(duì)象遇西,但是,一個(gè)沒有生命周期的Activity對(duì)象有什么用严嗜?所以在Android系統(tǒng)上粱檀,僅僅完成動(dòng)態(tài)類加載是不夠的;我們需要想辦法把我們加載進(jìn)來的Activity等組件交給系統(tǒng)管理漫玄,讓AMS
賦予組件生命周期茄蚯;這樣才算是一個(gè)有血有肉的完善的插件化方案。
接下來的系列文章會(huì)講述 DroidPlugin對(duì)于Android四大組件的處理方式睦优,我們且看它如何采用Hook技術(shù)坑蒙拐騙把系統(tǒng)玩弄于股掌之中渗常,最終賦予Activity,Service等組件生命周期汗盘,完成借尸還魂的皱碘。
首先,我們來看看DroidPlugin對(duì)于Activity
組件的處理方式隐孽。
閱讀本文之前癌椿,可以先clone一份 understand-plugin-framework,參考此項(xiàng)目的intercept-activity模塊菱阵。另外踢俄,如果對(duì)于Hook技術(shù)不甚了解,請(qǐng)先查閱我之前的文章:
AndroidManifest.xml的限制
讀到這里送粱,或許有部分讀者覺得疑惑了褪贵,啟動(dòng)Activity不就是一個(gè)startActivity
的事嗎,有這么神秘兮兮的?
啟動(dòng)Activity確實(shí)非常簡(jiǎn)單脆丁,但是Android卻有一個(gè)限制:必須在AndroidManifest.xml中顯示聲明使用的Activity世舰;我相信讀者肯定會(huì)遇到下面這種異常:
03-18 15:29:56.074 20709-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 activity class {com.weishu.intercept_activity.app/com.weishu.intercept_activity.app.TargetActivity}; have you declared this activity in your AndroidManifest.xml?
『必須在AndroidManifest.xml中顯示聲明使用的Activity』這個(gè)硬性要求很大程度上限制了插件系統(tǒng)的發(fā)揮:假設(shè)我們需要啟動(dòng)一個(gè)插件的Activity,插件使用的Activity是無法預(yù)知的槽卫,這樣肯定也不會(huì)在Manifest文件中聲明跟压;如果插件新添加一個(gè)Activity,主程序的AndroidManifest.xml就需要更新歼培;既然雙方都需要修改升級(jí)震蒋,何必要使用插件呢?這已經(jīng)違背了動(dòng)態(tài)加載的初衷:不修改插件框架而動(dòng)態(tài)擴(kuò)展功能躲庄。
能不能想辦法繞過這個(gè)限制呢查剖?
束手無策啊,怎么辦噪窘?借刀殺人偷梁換柱無中生有以逸待勞乘火打劫瞞天過海...等等笋庄!偷梁換柱瞞天過海?貌似可以一試倔监。
我們可以耍個(gè)障眼法:既然AndroidManifest文件中必須聲明直砂,那么我就聲明一個(gè)(或者有限個(gè))替身Activity好了,當(dāng)需要啟動(dòng)插件的某個(gè)Activity的時(shí)候浩习,先讓系統(tǒng)以為啟動(dòng)的是AndroidManifest中聲明的那個(gè)替身静暂,暫時(shí)騙過系統(tǒng);然后到合適的時(shí)候又替換回我們需要啟動(dòng)的真正的Activity谱秽;所謂瞞天過海洽蛀,莫過如此!
現(xiàn)在有了方案了弯院,但是該如何做呢辱士?兵書又說,知己知彼百戰(zhàn)不殆听绳!如果連Activity的啟動(dòng)過程都不熟悉颂碘,怎么完成這個(gè)瞞天過海的過程?
Activity啟動(dòng)過程
啟動(dòng)Activity非常簡(jiǎn)單椅挣,一個(gè)startActivity
就完事了头岔;那么在這個(gè)簡(jiǎn)單調(diào)用的背后發(fā)生了什么呢?Look the fucking source code鼠证!
關(guān)于Activity 的啟動(dòng)過程峡竣,也不是三言兩語能解釋清楚的,如果按照源碼一步一步走下來量九,插件化系列文章就不用寫了适掰;所以這里我就給出一個(gè)大致流程颂碧,只列出關(guān)鍵的調(diào)用點(diǎn)(以Android 6.0源碼為例);如果讀者希望更詳細(xì)的講解类浪,可以參考老羅的 Android應(yīng)用程序的Activity啟動(dòng)過程簡(jiǎn)要介紹和學(xué)習(xí)計(jì)劃
首先是Activity類的startActivity
方法:
public void startActivity(Intent intent) {
startActivity(intent, null);
}
跟著這個(gè)方法一步一步跟蹤载城,會(huì)發(fā)現(xiàn)它最后在startActivityForResult
里面調(diào)用了Instrument對(duì)象的execStartActivity
方法;接著在這個(gè)函數(shù)里面調(diào)用了ActivityManagerNative類的startActivity
方法费就;這個(gè)過程在前文已經(jīng)反復(fù)舉例講解了诉瓦,我們知道接下來會(huì)通過Binder IPC到AMS
所在進(jìn)程調(diào)用AMS
的startActivity
方法;Android系統(tǒng)的組件生命周期管理就是在AMS
里面完成的力细,那么在AMS
里面到底做了什么呢睬澡?
ActivityManagerService的startActivity
方法如下:
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags,
String profileFile, ParcelFileDescriptor profileFd, Bundle options) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode,
startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId());
}
很簡(jiǎn)單,直接調(diào)用了startActivityAsUser
這個(gè)方法眠蚂;接著是ActivityStackSupervisor
類的startActivityMayWait
方法煞聪。這個(gè)ActivityStackSupervisor類到底是個(gè)啥?如果仔細(xì)查閱逝慧,低版本的Android源碼上是沒有這個(gè)類的米绕;后來AMS的代碼進(jìn)行了部分重構(gòu),關(guān)于Activity棧管理的部分單獨(dú)提取出來成為了ActivityStackSupervisor
類馋艺;好了,繼續(xù)看代碼迈套。
startActivityMayWait這個(gè)方法前面對(duì)參數(shù)進(jìn)行了一系列處理捐祠,我們需要知道的是,在這個(gè)方法內(nèi)部對(duì)傳進(jìn)來的Intent進(jìn)行了解析桑李,并嘗試從中取出關(guān)于啟動(dòng)Activity的信息踱蛀。
然后這個(gè)方法調(diào)用了startActivityLocked方法;在startActivityLocked方法內(nèi)部進(jìn)行了一系列重要的檢查:比如權(quán)限檢查贵白,Activity的exported屬性檢查等等率拒;我們上文所述的,啟動(dòng)沒有在Manifestfest中顯示聲明的Activity拋異常也是這里發(fā)生的:
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返回之后會(huì)檢查這個(gè)值猬膨,然后跑出異常:
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
源碼看到這里,我們已經(jīng)確認(rèn)了『必須在AndroidManifest.xml中顯示聲明使用的Activity』的原因呛伴;然而這個(gè)校檢過程發(fā)生在AMS
所在的進(jìn)程system_server
勃痴,我們沒有辦法篡改,只能另尋他路热康。
OK沛申,我們繼續(xù)跟蹤源碼;在startActivityLocked之后處理的都是Activity任務(wù)棧相關(guān)內(nèi)容姐军,這一系列ActivityStack和ActivityStackSupervisor糾纏不清的調(diào)用看下圖就明白了铁材;不明白也沒關(guān)系: D 目前用處不大尖淘。
這一系列調(diào)用最終到達(dá)了ActivityStackSupervisor的realStartActivityLocked方法;人如其名著觉,這個(gè)方法開始了真正的“啟動(dòng)Activity”:它調(diào)用了ApplicationThread的scheduleLaunchActivity方法村生,開始了真正的Activity對(duì)象創(chuàng)建以及啟動(dòng)過程。
這個(gè)ApplicationThread是什么固惯,是一個(gè)線程嗎梆造?與ActivityThread有什么區(qū)別和聯(lián)系?
不要被名字迷惑了葬毫,這個(gè)ApplicationThread實(shí)際上是一個(gè)Binder對(duì)象镇辉,是App所在的進(jìn)程與AMS所在進(jìn)程system_server通信的橋梁;在Activity啟動(dòng)的過程中贴捡,App進(jìn)程會(huì)頻繁地與AMS進(jìn)程進(jìn)行通信:
- App進(jìn)程會(huì)委托AMS進(jìn)程完成Activity生命周期的管理以及任務(wù)棧的管理忽肛;這個(gè)通信過程AMS是Server端,App進(jìn)程通過持有AMS的client代理ActivityManagerNative完成通信過程烂斋;
- AMS進(jìn)程完成生命周期管理以及任務(wù)棧管理后屹逛,會(huì)把控制權(quán)交給App進(jìn)程,讓App進(jìn)程完成Activity類對(duì)象的創(chuàng)建汛骂,以及生命周期回調(diào)罕模;這個(gè)通信過程也是通過Binder完成的,App所在server端的Binder對(duì)象存在于ActivityThread的內(nèi)部類ApplicationThread帘瞭;AMS所在client通過持有IApplicationThread的代理對(duì)象完成對(duì)于App進(jìn)程的通信淑掌。
App進(jìn)程與AMS進(jìn)程的通信過程如圖所示:
App進(jìn)程內(nèi)部的ApplicationThread server端內(nèi)部有自己的Binder線程池,它與App主線程的通信通過Handler完成蝶念,這個(gè)Handler存在于ActivityThread類抛腕,它的名字很簡(jiǎn)單就叫H
,這一點(diǎn)我們接下來就會(huì)講到媒殉。
現(xiàn)在我們明白了這個(gè)ApplicationThread到底是個(gè)什么東西担敌,接上文繼續(xù)跟蹤Activity的啟動(dòng)過程;我們查看ApplicationThread的scheduleLaunchActivity
方法廷蓉,這個(gè)方法很簡(jiǎn)單全封,就是包裝了參數(shù)最終使用Handler發(fā)了一個(gè)消息。
正如剛剛所說桃犬,ApplicationThread所在的Binder服務(wù)端使用Handler與主線程進(jìn)行通信售貌,這里的scheduleLaunchActivity方法直接把啟動(dòng)Activity的任務(wù)通過一個(gè)消息轉(zhuǎn)發(fā)給了主線程;我們查看Handler類對(duì)于這個(gè)消息的處理:
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方法颂跨,在這個(gè)方法內(nèi)部有一句非常重要:
Activity a = performLaunchActivity(r, customIntent);
繞了這么多彎,我們的Activity終于被創(chuàng)建出來了扯饶!繼續(xù)跟蹤這個(gè)performLaunchActivity方法看看發(fā)生了什么恒削;由于這個(gè)方法較長(zhǎng)池颈,我就不貼代碼了,讀者可以自行查閱钓丰;要指出的是躯砰,這個(gè)方法做了兩件很重要的事情:
- 使用ClassLoader加載并通過反射創(chuàng)建Activity對(duì)象
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對(duì)象并回調(diào)相應(yīng)的生命周期方法携丁;
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// ... 省略
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
Activity的啟動(dòng)過程到這里就結(jié)束了琢歇,可能讀者還是覺得迷惑:不就是調(diào)用了一系列方法嗎?具體做了什么還是不太清楚梦鉴,而且為什么Android要這么設(shè)計(jì)李茫?
方法調(diào)用鏈再長(zhǎng)也木有關(guān)系,有兩點(diǎn)需要明白:
- 平時(shí)我們所說的Application被創(chuàng)建了肥橙,onCreate方法被調(diào)用了魄宏,我們或許并沒有意識(shí)到我們所說的
Application, Activity
除了代表Android應(yīng)用層通常所代表的“組件”之外,它們其實(shí)都是普通的Java對(duì)象存筏,也是需要被構(gòu)造函數(shù)構(gòu)造出來的對(duì)象的宠互;在這個(gè)過程中,我們明白了這些對(duì)象到底是如何被創(chuàng)建的椭坚。 - 為什么需要一直與AMS進(jìn)行通信予跌?哪些操作是在AMS中進(jìn)行的?其實(shí)
AMS
正如名字所說善茎,管理所有的“活動(dòng)”匕得,整個(gè)系統(tǒng)的Activity堆棧,Activity生命周期回調(diào)都是由AMS所在的系統(tǒng)進(jìn)程system_server幫開發(fā)者完成的巾表;Android的Framework層幫忙完成了諸如生命周期管理等繁瑣復(fù)雜的過程,簡(jiǎn)化了應(yīng)用層的開發(fā)略吨。
瞞天過杭遥——啟動(dòng)不在AndroidManifest.xml中聲明的Activity
通過上文的分析,我們已經(jīng)對(duì)Activity的啟動(dòng)過程了如指掌了翠忠;就讓我們干點(diǎn)壞事吧 :D
對(duì)與『必須在AndroidManifest.xml中顯示聲明使用的Activity』這個(gè)問題鞠苟,上文給出了思路——瞞天過海;我們可以在AndroidManifest.xml里面聲明一個(gè)替身Activity秽之,然后在合適的時(shí)候把這個(gè)假的替換成我們真正需要啟動(dòng)的Activity就OK了当娱。
那么問題來了,『合適的時(shí)候』到底是什么時(shí)候考榨?在前文Hook機(jī)制之動(dòng)態(tài)代理中我們提到過Hook過程最重要的一步是尋找Hook點(diǎn)跨细;如果是在同一個(gè)進(jìn)程,startActivity
到Activity真正啟動(dòng)起來這么長(zhǎng)的調(diào)用鏈河质,我們隨便找個(gè)地方Hook掉就完事兒了冀惭;但是問題木有這么簡(jiǎn)單震叙。
Activity啟動(dòng)過程中很多重要的操作(正如上文分析的『必須在AndroidManifest.xml中顯式聲明要啟動(dòng)的Activity』)都不是在App進(jìn)程里面執(zhí)行的,而是在AMS所在的系統(tǒng)進(jìn)程system_server完成散休,由于進(jìn)程隔離的存在媒楼,我們對(duì)別的進(jìn)程無能為力;所以這個(gè)Hook點(diǎn)就需要花點(diǎn)心思了戚丸。
這時(shí)候Activity啟動(dòng)過程的知識(shí)就派上用場(chǎng)了划址;雖然整個(gè)啟動(dòng)過程非常復(fù)雜,但其實(shí)一張圖就能總結(jié):
先從App進(jìn)程調(diào)用startActivity
限府;然后通過IPC調(diào)用進(jìn)入系統(tǒng)進(jìn)程system_server夺颤,完成Activity管理以及一些校檢工作,最后又回到了APP進(jìn)程完成真正的Activioty對(duì)象創(chuàng)建谣殊。
由于這個(gè)檢驗(yàn)過程是在AMS進(jìn)程完成的拂共,我們對(duì)system_server進(jìn)程里面的操作無能為力,只有在我們APP進(jìn)程里面執(zhí)行的過程才是有可能被Hook掉的姻几,也就是第一步和第三步宜狐;具體應(yīng)該怎么辦呢?
既然需要一個(gè)顯式聲明的Activity蛇捌,那就聲明一個(gè)抚恒!可以在第一步假裝啟動(dòng)一個(gè)已經(jīng)在AndroidManifest.xml里面聲明過的替身Activity,讓這個(gè)Activity進(jìn)入AMS進(jìn)程接受檢驗(yàn)络拌;最后在第三步的時(shí)候換成我們真正需要啟動(dòng)的Activity俭驮;這樣就成功欺騙了AMS進(jìn)程,瞞天過海春贸!
說到這里混萝,是不是有點(diǎn)小激動(dòng)呢?我們寫個(gè)demo驗(yàn)證一下:『?jiǎn)?dòng)一個(gè)并沒有在AndroidManifest.xml中顯示聲明的Activity』
實(shí)戰(zhàn)過程
具體來說萍恕,我們打算實(shí)現(xiàn)如下功能:在MainActivity中啟動(dòng)一個(gè)并沒有在AndroidManifest.xml中聲明的TargetActivity逸嘀;按照上文分析,我們需要聲明一個(gè)替身Activity允粤,我們叫它StubActivity崭倘;
那么,我們的AndroidManifest.xml如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.weishu.intercept_activity.app">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- 替身Activity, 用來欺騙AMS -->
<activity android:name=".StubActivity"/>
</application>
</manifest>
OK类垫,那么我們啟動(dòng)TargetActivity很簡(jiǎn)單司光,就是個(gè)startActivity
調(diào)用的事:
startActivity(new Intent(MainActivity.this, TargetActivity.class));
如果你直接這么運(yùn)行,肯定會(huì)直接拋出ActivityNotFoundException然后直接退出悉患;我們接下來要做的就是讓這個(gè)調(diào)用成功啟動(dòng)TargetActivity残家。
貍貓換太子——使用替身Activity繞過AMS
由于AMS
進(jìn)程會(huì)對(duì)Activity做顯式聲明驗(yàn)證,因此在
啟動(dòng)Activity的控制權(quán)轉(zhuǎn)移到AMS
進(jìn)程之前售躁,我們需要想辦法臨時(shí)把TargetActivity替換成替身StubActivity跪削;在這之間有很長(zhǎng)的一段調(diào)用鏈谴仙,我們可以輕松Hook掉;選擇什么地方Hook是一個(gè)很自由的事情碾盐,但是Hook的步驟越后越可靠——Hook得越早晃跺,后面的調(diào)用就越復(fù)雜,越容易出錯(cuò)毫玖。
我們可以選擇在進(jìn)入AMS
進(jìn)程的入口進(jìn)行Hook掀虎,具體來說也就是Hook AMS
在本進(jìn)程的代理對(duì)象ActivityManagerNative。如果你不知道如何Hook掉這個(gè)AMS的代理對(duì)象付枫,請(qǐng)查閱我之前的文章 Hook機(jī)制之AMS&PMS
我們Hook掉ActivityManagerNative對(duì)于startActivity方法的調(diào)用烹玉,替換掉交給AMS的intent對(duì)象,將里面的TargetActivity的暫時(shí)替換成已經(jīng)聲明好的替身StubActivity阐滩;這種Hook方式 前文 講述的很詳細(xì)二打,不贅述;替換的關(guān)鍵代碼如下:
if ("startActivity".equals(method.getName())) {
// 只攔截這個(gè)方法
// 替換參數(shù), 任你所為;甚至替換原始Activity啟動(dòng)別的Activity偷梁換柱
// API 23:
// public final Activity startActivityNow(Activity parent, String id,
// Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
// Activity.NonConfigurationInstances lastNonConfigurationInstances) {
// 找到參數(shù)里面的第一個(gè)Intent 對(duì)象
Intent raw;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
raw = (Intent) args[index];
Intent newIntent = new Intent();
// 這里包名直接寫死,如果再插件里,不同的插件有不同的包 傳遞插件的包名即可
String targetPackage = "com.weishu.intercept_activity.app";
// 這里我們把啟動(dòng)的Activity臨時(shí)替換為 StubActivity
ComponentName componentName = new ComponentName(targetPackage, StubActivity.class.getCanonicalName());
newIntent.setComponent(componentName);
// 把我們?cè)家獑?dòng)的TargetActivity先存起來
newIntent.putExtra(HookHelper.EXTRA_TARGET_INTENT, raw);
// 替換掉Intent, 達(dá)到欺騙AMS的目的
args[index] = newIntent;
Log.d(TAG, "hook success");
return method.invoke(mBase, args);
}
return method.invoke(mBase, args);
通過這個(gè)替換過程掂榔,在ActivityManagerNative的startActivity調(diào)用之后继效,system_server端收到Binder驅(qū)動(dòng)的消息,開始執(zhí)行ActivityManagerService里面真正的startActivity
方法装获;這時(shí)候AMS看到的intent
參數(shù)里面的組件已經(jīng)是StubActivity
了瑞信,因此可以成功繞過檢查,這時(shí)候如果不做后面的Hook穴豫,直接調(diào)用
startActivity(new Intent(MainActivity.this, TargetActivity.class));
也不會(huì)出現(xiàn)上文的ActivityNotFoundException
借尸還魂——攔截Callback從恢復(fù)真身
行百里者半九十》布颍現(xiàn)在我們的startActivity
啟動(dòng)一個(gè)沒有顯式聲明的Activity已經(jīng)不會(huì)拋異常了,但是要真正正確地把TargetActivity啟動(dòng)起來精肃,還有一些事情要做秤涩。其中最重要的一點(diǎn)是,我們用替身StubActivity臨時(shí)換了TargetActivity司抱,肯定需要在『合適的』時(shí)候替換回來筐眷;接下來我們就完成這個(gè)過程。
在AMS進(jìn)程里面我們是沒有辦法換回來的状植,因此我們要等AMS把控制權(quán)交給App所在進(jìn)程,也就是上面那個(gè)『Activity啟動(dòng)過程簡(jiǎn)圖』的第三步怨喘。AMS進(jìn)程轉(zhuǎn)移到App進(jìn)程也是通過Binder調(diào)用完成的津畸,承載這個(gè)功能的Binder對(duì)象是IApplicationThread;在App進(jìn)程它是Server端必怜,在Server端接受Binder遠(yuǎn)程調(diào)用的是Binder線程池肉拓,Binder線程池通過Handler將消息轉(zhuǎn)發(fā)給App的主線程;(我這里不厭其煩地?cái)⑹鯞inder調(diào)用過程梳庆,希望讀者不要反感暖途,其一加深印象卑惜,其二懂Binder真的很重要)我們可以在這個(gè)Handler里面將替身恢復(fù)成真身。
這里不打算講述Handler 的原理驻售,我們簡(jiǎn)單看一下Handler是如何處理接收到的Message的露久,如果我們能攔截這個(gè)Message的接收過程,就有可能完成替身恢復(fù)工作欺栗;Handler類的dispathMesage
如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
從這個(gè)方法可以看出來毫痕,Handler類消息分發(fā)的過程如下:
- 如果傳遞的Message本身就有callback,那么直接使用Message對(duì)象的callback方法迟几;
- 如果Handler類的成員變量
mCallback
存在消请,那么首先執(zhí)行這個(gè)mCallback
回調(diào); - 如果
mCallback
的回調(diào)返回true
类腮,那么表示消息已經(jīng)成功處理臊泰;直接結(jié)束。 - 如果
mCallback
的回調(diào)返回false
蚜枢,那么表示消息沒有處理完畢缸逃,會(huì)繼續(xù)使用Handler類的handleMessage
方法處理消息。
那么祟偷,ActivityThread中的Handler類H
是如何實(shí)現(xiàn)的呢察滑?H
的部分源碼如下:
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_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;
case RELAUNCH_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ā)過程得知修肠,我們可以攔截這一過程:把這個(gè)H
類的mCallback
替換為我們的自定義實(shí)現(xiàn)贺辰,這樣dispathMessage
就會(huì)首先使用這個(gè)自定義的mCallback
,然后看情況使用H
重載的handleMessage
嵌施。
這個(gè)Handler.Callback
是一個(gè)接口饲化,我們可以使用動(dòng)態(tài)代理或者普通代理完成Hook,這里我們使用普通的靜態(tài)代理方式吗伤;創(chuàng)建一個(gè)自定義的Callback類:
/* package */ class ActivityThreadHandlerCallback implements Handler.Callback {
Handler mBase;
public ActivityThreadHandlerCallback(Handler base) {
mBase = base;
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
// ActivityThread里面 "LAUNCH_ACTIVITY" 這個(gè)字段的值是100
// 本來使用反射的方式獲取最好, 這里為了簡(jiǎn)便直接使用硬編碼
case 100:
handleLaunchActivity(msg);
break;
}
mBase.handleMessage(msg);
return true;
}
private void handleLaunchActivity(Message msg) {
// 這里簡(jiǎn)單起見,直接取出TargetActivity;
Object obj = msg.obj;
// 根據(jù)源碼:
// 這個(gè)對(duì)象是 ActivityClientRecord 類型
// 我們修改它的intent字段為我們?cè)瓉肀4娴募纯?
/* 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 {
// 把替身恢復(fù)成真身
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();
}
}
}
這個(gè)Callback類的使命很簡(jiǎn)單:把替身StubActivity恢復(fù)成真身TargetActivity吃靠;有了這個(gè)自定義的Callback之后我們需要把ActivityThread里面處理消息的Handler類H
的的mCallback
修改為自定義callback類的對(duì)象:
// 先獲取到當(dāng)前的ActivityThread對(duì)象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
Object currentActivityThread = currentActivityThreadField.get(null);
// 由于ActivityThread一個(gè)進(jìn)程只有一個(gè),我們獲取這個(gè)對(duì)象的mH
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(currentActivityThread);
// 設(shè)置它的回調(diào), 根據(jù)源碼:
// 我們自己給他設(shè)置一個(gè)回調(diào),就會(huì)替代之前的回調(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, new ActivityThreadHandlerCallback(mH));
到這里,我們已經(jīng)成功地繞過AMS
足淆,完成了『?jiǎn)?dòng)沒有在AndroidManifest.xml中顯式聲明的Activity』的過程巢块;瞞天過海,這種玩弄系統(tǒng)與股掌之中的快感你們能體會(huì)到嗎巧号?
僵尸or活人族奢?——能正確收到生命周期回調(diào)嗎
雖然我們完成了『?jiǎn)?dòng)沒有在AndroidManifest.xml中顯式聲明的Activity 』,但是啟動(dòng)的TargetActivity是否有自己的生命周期呢丹鸿,我們還需要額外的處理過程嗎越走?
實(shí)際上TargetActivity已經(jīng)是一個(gè)有血有肉的Activity了:它具有自己正常的生命周期;可以運(yùn)行Demo代碼驗(yàn)證一下。
這個(gè)過程是如何完成的呢廊敌?我們以onDestroy
為例簡(jiǎn)要分析一下:
從Activity的
finish
方法開始跟蹤铜跑,最終會(huì)通過ActivityManagerNative到AMS
然后接著通過ApplicationThread到ActivityThread,然后通過H
轉(zhuǎn)發(fā)消息到ActivityThread的handleDestroyActivity骡澈,接著這個(gè)方法把任務(wù)交給performDestroyActivity完成锅纺。
在真正分析這個(gè)方法之前,需要說明一點(diǎn)的是:不知讀者是否感受得到秧廉,App進(jìn)程與AMS
交互幾乎都是這么一種模式伞广,幾個(gè)角色 ActivityManagerNative, ApplicationThread, ActivityThread以及Handler類H
分工明確,讀者可以按照這幾個(gè)角色的功能分析AMS
的任何調(diào)用過程疼电,屢試不爽嚼锄;這也是我的初衷——希望分析插件框架的過程中能幫助深入理解Android Framework。
好了繼續(xù)分析performDestroyActivity蔽豺,關(guān)鍵代碼如下:
ActivityClientRecord r = mActivities.get(token);
// ...略
mInstrumentation.callActivityOnDestroy(r.activity);
這里通過mActivities
拿到了一個(gè)ActivityClientRecord区丑,然后直接把這個(gè)record里面的Activity交給Instrument類完成了onDestroy的調(diào)用。
在我們這個(gè)demo的場(chǎng)景下修陡,r.activity是TargetActivity還是StubActivity沧侥?按理說,由于我們欺騙了AMS
魄鸦,AMS
應(yīng)該只知道StubActivity
的存在宴杀,它壓根兒就不知道TargetActivity是什么,為什么它能正確完成對(duì)TargetActivity生命周期的回調(diào)呢拾因?
一切的秘密在token
里面旺罢。AMS
與ActivityThread
之間對(duì)于Activity的生命周期的交互,并沒有直接使用Activity對(duì)象進(jìn)行交互绢记,而是使用一個(gè)token來標(biāo)識(shí)扁达,這個(gè)token是binder對(duì)象,因此可以方便地跨進(jìn)程傳遞蠢熄。Activity里面有一個(gè)成員變量mToken
代表的就是它跪解,token可以唯一地標(biāo)識(shí)一個(gè)Activity對(duì)象,它在Activity的attach
方法里面初始化签孔;
在AMS
處理Activity的任務(wù)棧的時(shí)候叉讥,使用這個(gè)token標(biāo)記Activity,因此在我們的demo里面饥追,AMS
進(jìn)程里面的token對(duì)應(yīng)的是StubActivity图仓,也就是AMS
還在傻乎乎地操作StubActivity(關(guān)于這一點(diǎn),你可以dump出任務(wù)棧的信息判耕,可以觀察到dump出的確實(shí)是StubActivity)透绩。但是在我們App進(jìn)程里面翘骂,token對(duì)應(yīng)的卻是TargetActivity壁熄!因此帚豪,在ActivityThread執(zhí)行回調(diào)的時(shí)候,能正確地回調(diào)到TargetActivity相應(yīng)的方法草丧。
為什么App進(jìn)程里面狸臣,token對(duì)應(yīng)的是TargetActivity呢?
回到代碼昌执,ActivityClientRecord是在mActivities
里面取出來的烛亦,確實(shí)是根據(jù)token取懂拾;那么這個(gè)token是什么時(shí)候添加進(jìn)去的呢煤禽?我們看performLaunchActivity就完成明白了:它通過classloader加載了TargetActivity,然后完成一切操作之后把這個(gè)activity添加進(jìn)了mActivities
岖赋!另外檬果,在這個(gè)方法里面我們還能看到對(duì)Ativityattact
方法的調(diào)用,它傳遞給了新創(chuàng)建的Activity一個(gè)token對(duì)象唐断,而這個(gè)token是在ActivityClientRecord構(gòu)造函數(shù)里面初始化的选脊。
至此我們已經(jīng)可以確認(rèn),通過這種方式啟動(dòng)的Activity有它自己完整而獨(dú)立的生命周期脸甘!
小節(jié)
本文講述了『?jiǎn)?dòng)一個(gè)并沒有在AndroidManifest.xml中顯示聲明的Activity』的解決辦法恳啥,我們成功地繞過了Android的這個(gè)限制,這個(gè)是插件Activity管理技術(shù)的基礎(chǔ)丹诀;但是要做到啟動(dòng)一個(gè)插件Activity問題遠(yuǎn)沒有這么簡(jiǎn)單钝的。
首先,在Android中忿墅,Activity有不同的啟動(dòng)模式扁藕;我們聲明了一個(gè)替身StubActivity,肯定沒有滿足所有的要求疚脐;因此亿柑,我們需要在AndroidManifest.xml中聲明一系列的有不同launchMode的Activity,還需要完成替身與真正Activity launchMode的匹配過程棍弄;這樣才能完成啟動(dòng)各種類型Activity的需求望薄,關(guān)于這一點(diǎn),在 DroidPlugin 的com.morgoo.droidplugin.stub包下面可以找到呼畸。
另外痕支,每啟動(dòng)一個(gè)插件的Activity都需要一個(gè)StubActivity,但是AndroidManifest.xml中肯定只能聲明有限個(gè)蛮原,如果一直startActivity
而不finish的話卧须,那么理論上就需要無限個(gè)StubActivity;這個(gè)問題該如何解決呢?事實(shí)上花嘶,這個(gè)問題在技術(shù)上沒有好的解決辦法笋籽。但是,如果你的App startActivity了十幾次椭员,而沒有finish任何一個(gè)Activity车海,這樣在Activity的回退棧里面有十幾個(gè)Activity,用戶難道按back十幾次回到主頁嗎隘击?有這種需求說明你的產(chǎn)品設(shè)計(jì)有問題侍芝;一個(gè)App一級(jí)頁面,二級(jí)頁面..到五六級(jí)的頁面已經(jīng)影響體驗(yàn)了埋同,所以州叠,每種LauchMode聲明十個(gè)StubActivity絕對(duì)能滿足需求了。
最后凶赁,在本文所述例子中留量,TargetActivity與StubActivity存在于同一個(gè)Apk,因此系統(tǒng)的ClassLoader能夠成功加載并創(chuàng)建TargetActivity的實(shí)例哟冬。但是在實(shí)際的插件系統(tǒng)中楼熄,要啟動(dòng)的目標(biāo)Activity肯定存在于一個(gè)單獨(dú)的文件中,系統(tǒng)默認(rèn)的ClassLoader無法加載插件中的Activity類——系統(tǒng)壓根兒就不知道要加載的插件在哪浩峡,談何加載可岂?因此還有一個(gè)很重要的問題需要處理:
我們要完成插件系統(tǒng)中類的加載,這可以通過自定義ClassLoader實(shí)現(xiàn)翰灾。解決了『?jiǎn)?dòng)沒有在AndroidManifest.xml中顯式聲明的缕粹,并且存在于外部文件中的Activity』的問題,插件系統(tǒng)對(duì)于Activity的管理才算得上是一個(gè)完全體纸淮。篇幅所限平斩,欲知后事如何,請(qǐng)聽下回分解咽块!
喜歡就點(diǎn)個(gè)贊吧~持續(xù)更新绘面,請(qǐng)關(guān)注github項(xiàng)目 understand-plugin-framework和我的 博客!