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

之前的 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)先查閱我之前的文章:

  1. Hook機(jī)制之動(dòng)態(tài)代理
  2. Hook機(jī)制之Binder Hook
  3. Hook機(jī)制之AMS&PMS

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)用AMSstartActivity方法;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)用流程圖

這一系列調(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)行通信:

  1. App進(jìn)程會(huì)委托AMS進(jìn)程完成Activity生命周期的管理以及任務(wù)棧的管理忽肛;這個(gè)通信過程AMS是Server端,App進(jìn)程通過持有AMS的client代理ActivityManagerNative完成通信過程烂斋;
  2. 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è)方法做了兩件很重要的事情:

  1. 使用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);
  1. 如果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)需要明白:

  1. 平時(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)建的椭坚。
  2. 為什么需要一直與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é):

簡(jiǎn)要啟動(dòng)過程

先從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ā)的過程如下:

  1. 如果傳遞的Message本身就有callback,那么直接使用Message對(duì)象的callback方法迟几;
  2. 如果Handler類的成員變量mCallback存在消请,那么首先執(zhí)行這個(gè)mCallback回調(diào);
  3. 如果mCallback的回調(diào)返回true类腮,那么表示消息已經(jīng)成功處理臊泰;直接結(jié)束。
  4. 如果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里面旺罢。AMSActivityThread之間對(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和我的 博客!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市侈沪,隨后出現(xiàn)的幾起案子揭璃,更是在濱河造成了極大的恐慌,老刑警劉巖亭罪,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘦馍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡应役,警方通過查閱死者的電腦和手機(jī)情组,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門燥筷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人院崇,你說我怎么就攤上這事荆责。” “怎么了亚脆?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)盲泛。 經(jīng)常有香客問我濒持,道長(zhǎng),這世上最難降的妖魔是什么寺滚? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任柑营,我火速辦了婚禮,結(jié)果婚禮上村视,老公的妹妹穿的比我還像新娘官套。我一直安慰自己,他們只是感情好蚁孔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布奶赔。 她就那樣靜靜地躺著,像睡著了一般杠氢。 火紅的嫁衣襯著肌膚如雪站刑。 梳的紋絲不亂的頭發(fā)上凄贩,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天劳翰,我揣著相機(jī)與錄音社搅,去河邊找鬼樱哼。 笑死句柠,一個(gè)胖子當(dāng)著我的面吹牛肠虽,可吹牛的內(nèi)容都是我干的嘶居。 我是一名探鬼主播峻黍,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼勺爱,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼晃琳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起琐鲁,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤蝎土,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后绣否,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體誊涯,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年蒜撮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暴构。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跪呈。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖取逾,靈堂內(nèi)的尸體忽然破棺而出耗绿,到底是詐尸還是另有隱情,我是刑警寧澤砾隅,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布误阻,位于F島的核電站,受9級(jí)特大地震影響晴埂,放射性物質(zhì)發(fā)生泄漏究反。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一儒洛、第九天 我趴在偏房一處隱蔽的房頂上張望精耐。 院中可真熱鬧,春花似錦琅锻、人聲如沸卦停。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惊完。三九已至,卻和暖如春处硬,著一層夾襖步出監(jiān)牢的瞬間专执,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工郁油, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留本股,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓桐腌,卻偏偏與公主長(zhǎng)得像拄显,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子案站,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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