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

之前的 Android插件化原理解析 系列文章揭開了Hook機制的神秘面紗,現(xiàn)在我們手握倚天屠龍,那么如何通過這種技術(shù)完成插件化方案呢?具體來說,插件中的Activity,Service等組件如何在Android系統(tǒng)上運行起來?

在Java平臺要做到動態(tài)運行模塊、熱插拔可以使用ClassLoader技術(shù)進行動態(tài)類加載,比如廣泛使用的OSGi技術(shù)。在Android上當然也可以使用動態(tài)加載技術(shù),但是僅僅把類加載進來就足夠了嗎?Activity,Service等組件是有生命周期的,它們統(tǒng)一由系統(tǒng)服務(wù)AMS管理;使用ClassLoader可以從插件中創(chuàng)建Activity對象,但是,一個沒有生命周期的Activity對象有什么用?所以在Android系統(tǒng)上,僅僅完成動態(tài)類加載是不夠的;我們需要想辦法把我們加載進來的Activity等組件交給系統(tǒng)管理,讓AMS賦予組件生命周期;這樣才算是一個有血有肉的完善的插件化方案。

接下來的系列文章會講述 DroidPlugin對于Android四大組件的處理方式,我們且看它如何采用Hook技術(shù)坑蒙拐騙把系統(tǒng)玩弄于股掌之中,最終賦予Activity,Service等組件生命周期,完成借尸還魂的。

首先,我們來看看DroidPlugin對于Activity組件的處理方式。

閱讀本文之前,可以先clone一份 understand-plugin-framework,參考此項目的intercept-activity模塊。另外,如果對于Hook技術(shù)不甚了解,請先查閱我之前的文章:

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

AndroidManifest.xml的限制

讀到這里,或許有部分讀者覺得疑惑了,啟動Activity不就是一個startActivity的事嗎,有這么神秘兮兮的?

啟動Activity確實非常簡單,但是Android卻有一個限制:必須在AndroidManifest.xml中顯示聲明使用的Activity;我相信讀者肯定會遇到下面這種異常:

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』這個硬性要求很大程度上限制了插件系統(tǒng)的發(fā)揮:假設(shè)我們需要啟動一個插件的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!

關(guān)于Activity 的啟動過程,也不是三言兩語能解釋清楚的,如果按照源碼一步一步走下來,插件化系列文章就不用寫了;所以這里我就給出一個大致流程,只列出關(guān)鍵的調(diào)用點(以Android 6.0源碼為例);如果讀者希望更詳細的講解,可以參考老羅的 Android應用程序的Activity啟動過程簡要介紹和學習計劃

首先是Activity類的startActivity方法:

public void startActivity(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)用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());
}

很簡單,直接調(diào)用了startActivityAsUser這個方法;接著是ActivityStackSupervisor類的startActivityMayWait方法。這個ActivityStackSupervisor類到底是個啥?如果仔細查閱,低版本的Android源碼上是沒有這個類的;后來AMS的代碼進行了部分重構(gòu),關(guān)于Activity棧管理的部分單獨提取出來成為了ActivityStackSupervisor類;好了,繼續(xù)看代碼。

startActivityMayWait這個方法前面對參數(shù)進行了一系列處理,我們需要知道的是,在這個方法內(nèi)部對傳進來的Intent進行了解析,并嘗試從中取出關(guān)于啟動Activity的信息。

然后這個方法調(diào)用了startActivityLocked方法;在startActivityLocked方法內(nèi)部進行了一系列重要的檢查:比如權(quán)限檢查,Activity的exported屬性檢查等等;我們上文所述的,啟動沒有在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返回之后會檢查這個值,然后跑出異常:

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)確認了『必須在AndroidManifest.xml中顯示聲明使用的Activity』的原因;然而這個校檢過程發(fā)生在AMS所在的進程system_server,我們沒有辦法篡改,只能另尋他路。

OK,我們繼續(xù)跟蹤源碼;在startActivityLocked之后處理的都是Activity任務(wù)棧相關(guān)內(nèi)容,這一系列ActivityStack和ActivityStackSupervisor糾纏不清的調(diào)用看下圖就明白了;不明白也沒關(guān)系: 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進程進行通信:

  1. App進程會委托AMS進程完成Activity生命周期的管理以及任務(wù)棧的管理;這個通信過程AMS是Server端,App進程通過持有AMS的client代理ActivityManagerNative完成通信過程;
  2. AMS進程完成生命周期管理以及任務(wù)棧管理后,會把控制權(quán)交給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服務(wù)端使用Handler與主線程進行通信,這里的scheduleLaunchActivity方法直接把啟動Activity的任務(wù)通過一個消息轉(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)部有一句非常重要:

Activity a = performLaunchActivity(r, customIntent);

繞了這么多彎,我們的Activity終于被創(chuàng)建出來了!繼續(xù)跟蹤這個performLaunchActivity方法看看發(fā)生了什么;由于這個方法較長,我就不貼代碼了,讀者可以自行查閱;要指出的是,這個方法做了兩件很重要的事情:

  1. 使用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);
  1. 如果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要這么設(shè)計?

方法調(diào)用鏈再長也木有關(guān)系,有兩點需要明白:

  1. 平時我們所說的Application被創(chuàng)建了,onCreate方法被調(diào)用了,我們或許并沒有意識到我們所說的Application, Activity除了代表Android應用層通常所代表的“組件”之外,它們其實都是普通的Java對象,也是需要被構(gòu)造函數(shù)構(gòu)造出來的對象的;在這個過程中,我們明白了這些對象到底是如何被創(chuàng)建的。
  2. 為什么需要一直與AMS進行通信?哪些操作是在AMS中進行的?其實AMS正如名字所說,管理所有的“活動”,整個系統(tǒng)的Activity堆棧,Activity生命周期回調(diào)都是由AMS所在的系統(tǒng)進程system_server幫開發(fā)者完成的;Android的Framework層幫忙完成了諸如生命周期管理等繁瑣復雜的過程,簡化了應用層的開發(fā)。

瞞天過?!獑硬辉贏ndroidManifest.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如下:

<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,那么我們啟動TargetActivity很簡單,就是個startActivity調(diào)用的事:

startActivity(new Intent(MainActivity.this, TargetActivity.class));

如果你直接這么運行,肯定會直接拋出ActivityNotFoundException然后直接退出;我們接下來要做的就是讓這個調(diào)用成功啟動TargetActivity。

貍貓換太子——使用替身Activity繞過AMS

由于AMS進程會對Activity做顯式聲明驗證,因此在
啟動Activity的控制權(quán)轉(zhuǎn)移到AMS進程之前,我們需要想辦法臨時把TargetActivity替換成替身StubActivity;在這之間有很長的一段調(diào)用鏈,我們可以輕松Hook掉;選擇什么地方Hook是一個很自由的事情,但是Hook的步驟越后越可靠——Hook得越早,后面的調(diào)用就越復雜,越容易出錯。

我們可以選擇在進入AMS進程的入口進行Hook,具體來說也就是Hook AMS在本進程的代理對象ActivityManagerNative。如果你不知道如何Hook掉這個AMS的代理對象,請查閱我之前的文章 Hook機制之AMS&PMS

我們Hook掉ActivityManagerNative對于startActivity方法的調(diào)用,替換掉交給AMS的intent對象,將里面的TargetActivity的暫時替換成已經(jīng)聲明好的替身StubActivity;這種Hook方式 前文 講述的很詳細,不贅述;替換的關(guān)鍵代碼如下:

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;
    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";

    // 這里我們把啟動的Activity臨時替換為 StubActivity
    ComponentName componentName = new ComponentName(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");
    return method.invoke(mBase, args);

}

return method.invoke(mBase, args);

通過這個替換過程,在ActivityManagerNative的startActivity調(diào)用之后,system_server端收到Binder驅(qū)動的消息,開始執(zhí)行ActivityManagerService里面真正的startActivity方法;這時候AMS看到的intent參數(shù)里面的組件已經(jīng)是StubActivity了,因此可以成功繞過檢查,這時候如果不做后面的Hook,直接調(diào)用

startActivity(new Intent(MainActivity.this, TargetActivity.class));

也不會出現(xiàn)上文的ActivityNotFoundException

借尸還魂——攔截Callback從恢復真身

行百里者半九十?,F(xiàn)在我們的startActivity啟動一個沒有顯式聲明的Activity已經(jīng)不會拋異常了,但是要真正正確地把TargetActivity啟動起來,還有一些事情要做。其中最重要的一點是,我們用替身StubActivity臨時換了TargetActivity,肯定需要在『合適的』時候替換回來;接下來我們就完成這個過程。

在AMS進程里面我們是沒有辦法換回來的,因此我們要等AMS把控制權(quán)交給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如下:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

從這個方法可以看出來,Handler類消息分發(fā)的過程如下:

  1. 如果傳遞的Message本身就有callback,那么直接使用Message對象的callback方法;
  2. 如果Handler類的成員變量mCallback存在,那么首先執(zhí)行這個mCallback回調(diào);
  3. 如果mCallback的回調(diào)返回true,那么表示消息已經(jīng)成功處理;直接結(jié)束。
  4. 如果mCallback的回調(diào)返回false,那么表示消息沒有處理完畢,會繼續(xù)使用Handler類的handleMessage方法處理消息。

那么,ActivityThread中的Handler類H是如何實現(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ā)過程得知,我們可以攔截這一過程:把這個H類的mCallback替換為我們的自定義實現(xiàn),這樣dispathMessage就會首先使用這個自定義的mCallback,然后看情況使用H重載的handleMessage。

這個Handler.Callback是一個接口,我們可以使用動態(tài)代理或者普通代理完成Hook,這里我們使用普通的靜態(tài)代理方式;創(chuàng)建一個自定義的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" 這個字段的值是100
            // 本來使用反射的方式獲取最好, 這里為了簡便直接使用硬編碼
            case 100:
                handleLaunchActivity(msg);
                break;
        }

        mBase.handleMessage(msg);
        return true;
    }

    private void handleLaunchActivity(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);

// 設(shè)置它的回調(diào), 根據(jù)源碼:
// 我們自己給他設(shè)置一個回調(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, new ActivityThreadHandlerCallback(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,接著這個方法把任務(wù)交給performDestroyActivity完成。

在真正分析這個方法之前,需要說明一點的是:不知讀者是否感受得到,App進程與AMS交互幾乎都是這么一種模式,幾個角色 ActivityManagerNative, ApplicationThread, ActivityThread以及Handler類H分工明確,讀者可以按照這幾個角色的功能分析AMS的任何調(diào)用過程,屢試不爽;這也是我的初衷——希望分析插件框架的過程中能幫助深入理解Android Framework。

好了繼續(xù)分析performDestroyActivity,關(guān)鍵代碼如下:

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里面。AMSActivityThread之間對于Activity的生命周期的交互,并沒有直接使用Activity對象進行交互,而是使用一個token來標識,這個token是binder對象,因此可以方便地跨進程傳遞。Activity里面有一個成員變量mToken代表的就是它,token可以唯一地標識一個Activity對象,它在Activity的attach方法里面初始化;

AMS處理Activity的任務(wù)棧的時候,使用這個token標記Activity,因此在我們的demo里面,AMS進程里面的token對應的是StubActivity,也就是AMS還在傻乎乎地操作StubActivity(關(guān)于這一點,你可以dump出任務(wù)棧的信息,可以觀察到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!另外,在這個方法里面我們還能看到對Ativityattact方法的調(diào)用,它傳遞給了新創(chuàng)建的Activity一個token對象,而這個token是在ActivityClientRecord構(gòu)造函數(shù)里面初始化的。

至此我們已經(jīng)可以確認,通過這種方式啟動的Activity有它自己完整而獨立的生命周期!

小節(jié)

本文講述了『啟動一個并沒有在AndroidManifest.xml中顯示聲明的Activity』的解決辦法,我們成功地繞過了Android的這個限制,這個是插件Activity管理技術(shù)的基礎(chǔ);但是要做到啟動一個插件Activity問題遠沒有這么簡單。

首先,在Android中,Activity有不同的啟動模式;我們聲明了一個替身StubActivity,肯定沒有滿足所有的要求;因此,我們需要在AndroidManifest.xml中聲明一系列的有不同launchMode的Activity,還需要完成替身與真正Activity launchMode的匹配過程;這樣才能完成啟動各種類型Activity的需求,關(guān)于這一點,在 DroidPlugin 的com.morgoo.droidplugin.stub包下面可以找到。

另外,每啟動一個插件的Activity都需要一個StubActivity,但是AndroidManifest.xml中肯定只能聲明有限個,如果一直startActivity而不finish的話,那么理論上就需要無限個StubActivity;這個問題該如何解決呢?事實上,這個問題在技術(shù)上沒有好的解決辦法。但是,如果你的App startActivity了十幾次,而沒有finish任何一個Activity,這樣在Activity的回退棧里面有十幾個Activity,用戶難道按back十幾次回到主頁嗎?有這種需求說明你的產(chǎn)品設(shè)計有問題;一個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的管理才算得上是一個完全體。篇幅所限,欲知后事如何,請聽下回分解!

喜歡就點個贊吧~持續(xù)更新,請關(guān)注github項目 understand-plugin-framework和我的 博客!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容