Android解析ActivityManagerService(二)ActivityTask和Activity棧管理

劉望舒發表於2017-09-03

相關文章
Android系統啟動流程系列
Android應用程式系列
Android深入四大元件系列
Android深入解析AMS系列

前言

關於AMS,原計劃是隻寫一篇文章來介紹,但是AMS功能繁多,一篇文章的篇幅遠遠不夠。這一篇我們接著來學習與AMS相關的ActivityTask和Activity棧管理。

1.ActivityStack

ActivityStack從名稱來看是跟棧相關的類,其實它是一個管理類,用來管理系統所有Activity的各種狀態。它由ActivityStackSupervisor來進行管理的,而ActivityStackSupervisor在AMS中的構造方法中被建立。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

   public ActivityManagerService(Context systemContext) {
   ...
    mStackSupervisor = new ActivityStackSupervisor(this);
   ... 
   }複製程式碼

1.1 ActivityStack的例項型別

ActivityStackSupervisor中有多種ActivityStack例項,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

public final class ActivityStackSupervisor implements DisplayListener {
   ...
    ActivityStack mHomeStack;
    ActivityStack mFocusedStack;
    private ActivityStack mLastFocusedStack;
    ...
}複製程式碼

mHomeStack用來儲存Launcher App的Activity的堆疊,mFocusedStack表示當前正在接收輸入或啟動下一個Activity的堆疊。mLastFocusedStack表示此前接收輸入的Activity的堆疊。

通過ActivityStackSupervisor提供了獲取上述ActivityStack的方法,比如要獲取mFocusedStack,只需要呼叫ActivityStackSupervisor的getFocusedStack方法就可以了:
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

   ActivityStack getFocusedStack() {
        return mFocusedStack;
    }複製程式碼

1.2 ActivityState

ActivityStack中通過列舉儲存了Activity的所有的狀態,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

 enum ActivityState {
        INITIALIZING,
        RESUMED,
        PAUSING,
        PAUSED,
        STOPPING,
        STOPPED,
        FINISHING,
        DESTROYING,
        DESTROYED
    }複製程式碼

通過名稱我們可以很輕易知道這些狀態所代表的意義。應用ActivityState的場景會有很多,比如下面的程式碼:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

   @Override
    public void overridePendingTransition(IBinder token, String packageName,
            int enterAnim, int exitAnim) {
     ...
            if (self.state == ActivityState.RESUMED
                    || self.state == ActivityState.PAUSING) {//1
                mWindowManager.overridePendingAppTransition(packageName,
                        enterAnim, exitAnim, null);
            }
            Binder.restoreCallingIdentity(origId);
        }
    }複製程式碼

overridePendingTransition方法用於設定Activity的切換動畫,註釋1處可以看到只有ActivityState為RESUMED狀態或者PAUSING狀態時才會呼叫WMS型別的mWindowManager物件的overridePendingAppTransition方法來進行切換動畫。

1.3 特殊狀態的Activity

在ActivityStack中定義了一些特殊狀態的Activity,如下所示。

ActivityRecord mPausingActivity = null;//正在暫停的Activity
ActivityRecord mLastPausedActivity = null;//上一個已經暫停的Activity
ActivityRecord mLastNoHistoryActivity = null;//最近一次沒有歷史記錄的Activity
ActivityRecord mResumedActivity = null;//已經Resume的Activity
ActivityRecord mLastStartedActivity = null;//最近一次啟動的Activity
ActivityRecord mTranslucentActivityWaiting = null;//傳遞給convertToTranslucent方法的最上層的Activity複製程式碼

這些特殊的狀態都是ActivityRecord型別的,ActivityRecord用來記錄一個Activity的所有資訊。從棧的角度來說(Activity任務棧是一個假想的模型),一個或多個ActivityRecord會組成一個TaskRecord,TaskRecord用來記錄Activity的棧,而ActivityStack包含了一個或多個TaskRecord。

1.4 維護的ArrayList

ActivityStack中維護了很多ArrayList,這些ArrayList中的元素型別主要有ActivityRecord和TaskRecord,其中TaskRecord用來記錄Activity的Task。

ArrayList 元素型別 說明
mTaskHistory TaskRecord 所有沒有被銷燬的Task
mLRUActivities ActivityRecord 正在執行的Activity,列表中的第一個條目是最近最少使用的元素
mNoAnimActivities ActivityRecord 不考慮轉換動畫的Activity
mValidateAppTokens TaskGroup 用於與視窗管理器驗證應用令牌

2.Activity棧管理

我們知道Activity是由任務棧來進行管理的,不過任務棧是一個假想的模型,並不真實的存在。棧管理就是建立在這個假想模型之上的,有了棧管理,我們可以對應用程式進行操作,應用可以複用自身應用中以及其他應用的Activity,節省了資源。比如我們使用一款社交應用,這個應用的聯絡人詳情介面提供了聯絡人的郵箱,當我們點選郵箱時會跳到傳送郵件的介面。

社交應用和系統Email中的Activity是處於不同應用程式程式的,而有了棧管理,就可以把傳送郵件介面放到社交應用中詳情介面所在棧的棧頂,來做到跨程式操作。
為了更靈活的進行棧管理,Android系統提供了很多配置,下面分別對它們進行介紹。

2.1 Launch Mode

Launch Mode都不會陌生,用於設定Activity的啟動方式,無論是哪種啟動方式,所啟動的Activity都會位於Activity棧的棧頂。有以下四種:

  • standerd:預設模式,每次啟動Activity都會建立一個新的Activity例項。
  • singleTop:如果要啟動的Activity已經在棧頂,則不會重新建立Activity,同時該Activity的onNewIntent方法會被呼叫。如果要啟動的Activity不在棧頂,則會重新建立該Activity的例項。
  • singleTask:如果要啟動的Activity已經存在於它想要歸屬的棧中,那麼不會建立該Activity例項,將棧中位於該Activity上的所有的Activity出棧,同時該Activity的onNewIntent方法會被呼叫。如果要啟動的Activity不存在於它想要歸屬的棧中,並且該棧存在,則會重新建立該Activity的例項。如果要啟動的Activity想要歸屬的棧不存在,則首先要建立一個新棧,然後建立該Activity例項並壓入到新棧中。
  • singleInstance:和singleTask基本類似,不同的是啟動Activity時,首先要建立在一個新棧,然後建立該Activity例項並壓入新棧中,新棧中只會存在這一個Activity例項。

2.2 Intent的FLAG

Intent中定義了很多了FLAG,其中有幾個FLAG也可以設定Activity的啟動方式,如果Launch Mode設定和FLAG設定的Activity的啟動方式有衝突,則以FLAG設定的為準。

  • FLAG_ACTIVITY_SINGLE_TOP:和Launch Mode中的singleTop效果是一樣的。
  • FLAG_ACTIVITY_NEW_TASK:和Launch Mode中的singleTask效果是一樣的。
  • FLAG_ACTIVITY_CLEAR_TOP:Launch Mode中沒有與此對應的模式,如果要啟動的Activity已經存在於棧中,則將所有位於它上面的Activity出棧。singleTask預設具有此標記位的效果。

除了這三個FLAG,還有一些FLAG對我們分析棧管理有些幫助。

  • FLAG_ACTIVITY_NO_HISTORY:Activity一旦退出,就不會存在於棧中。同樣的,也可以在AndroidManifest.xml中設定“android:noHistory”。
  • FLAG_ACTIVITY_MULTIPLE_TASK:需要和FLAG_ACTIVITY_NEW_TASK一同使用才有效果,系統會啟動一個新的棧來容納新啟動的Activity.
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:Activity不會被放入到“最近啟動的Activity”列表中。
  • FLAG_ACTIVITY_BROUGHT_TO_FRONT:這個標誌位通常不是由應用程式中的程式碼設定的,而是Launch Mode為singleTask時,由系統自動加上的。
  • FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY:這個標誌位通常不是由應用程式中的程式碼設定的,而是從歷史記錄中啟動的(長按Home鍵調出)。
  • FLAG_ACTIVITY_CLEAR_TASK:需要和FLAG_ACTIVITY_NEW_TASK一同使用才有效果,用於清除與啟動的Activity相關棧的所有其他Activity。

接下來通過系統原始碼來檢視FLAG的應用,在Android深入四大元件(一)應用程式啟動過程(後篇)中講過,根Activity啟動時會呼叫AMS的startActivity方法,經過層層呼叫會呼叫ActivityStarter的startActivityUnchecked方法,如下面的時序圖所示。

frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

 private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor);//1
        computeLaunchingTaskFlags();//2
        computeSourceStack();
        mIntent.setFlags(mLaunchFlags);//3
 ...       
}複製程式碼

註釋1處用於初始化啟動Activity的各種配置,在初始化前會重置各種配置再進行配置,這些配置包括:ActivityRecord、Intent、TaskRecord和LaunchFlags(啟動的FLAG)等等。註釋2處的computeLaunchingTaskFlags方法用於計算出啟動的FLAG,並將計算的值賦值給mLaunchFlags。在註釋3處將mLaunchFlags設定給Intent,達到設定Activity的啟動方式的目的。接著來檢視computeLaunchingTaskFlags方法。

frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

private void computeLaunchingTaskFlags() {
...
      if (mInTask == null) {//1
            if (mSourceRecord == null) {//2
                if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {//3
                    Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
                            "Intent.FLAG_ACTIVITY_NEW_TASK for: " + mIntent);
                    mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
                }
            } else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {//4
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            } else if (mLaunchSingleInstance || mLaunchSingleTask) {//5
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            }
        }
}複製程式碼

計算啟動的FLAG的邏輯比較複雜,這裡只擷取了一小部分,註釋1處的TaskRecord型別的mInTask為null時,說明Activity要加入的棧不存在。因此,這一小段程式碼主要解決的問題就是Activity要加入的棧不存在時如何計算出啟動的FLAG。註釋2處,ActivityRecord型別的mSourceRecord用於描述“初始Activity”,什麼是“初始Activity”呢?比如ActivityA啟動了ActivityB,ActivityA就是初始Activity。同時滿足註釋2和註釋3的條件則需要建立一個新棧。註釋4處,如果“初始Activity”所在的棧只允許有一個Activity例項,則也需要建立一個新棧。註釋5處,如果Launch Mode設定了singleTask或singleInstance,則也要建立一個新棧。

2.3 taskAffinity

我們可以在AndroidManifest.xml設定android:taskAffinity,用來指定Activity希望歸屬的棧, 預設情況下,同一個應用程式的所有的Activity都有著相同的taskAffinity。
taskAffinity在下面兩種情況時會產生效果。

  1. taskAffinity與FLAG_ACTIVITY_NEW_TASK或者singleTask配合。如果新啟動Activity的taskAffinity和棧的taskAffinity相同(棧的taskAffinity取決於根Activity的taskAffinity)則加入到該棧中。如果不同,就會建立新棧。
  2. taskAffinity與allowTaskReparenting配合。如果allowTaskReparenting為true,說明Activity具有轉移的能力。拿此前的郵件為例,當社交應用啟動了傳送郵件的Activity,此時傳送郵件的Activity是和社交應用處於同一個棧中。如果傳送郵件的Activity的allowTaskReparenting設定為true,此後郵件程式所在的棧位於前臺,這個時候傳送郵件的Activity就會由社交應用的棧中轉移到與它更親近的郵件程式(taskAffinity相同)所在的棧中。

接著通過系統原始碼來檢視taskAffinity的應用。ActivityStackSupervisor的findTaskLocked方法用於找到Activity最匹配的棧,最終會呼叫ActivityStack的findTaskLocked方法。
frameworks/base/services/core/java/com/android/server/am/ActivityStack.java

 void findTaskLocked(ActivityRecord target, FindTaskResult result) {
...
   for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {//1
     final TaskRecord task = mTaskHistory.get(taskNdx);//2
   ...
     else if (!isDocument && !taskIsDocument
                    && result.r == null && task.canMatchRootAffinity()) {
                if (task.rootAffinity.equals(target.taskAffinity)) {//3
                    if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching affinity candidate!");
                    result.r = r;
                    result.matchedByRootAffinity = true;
                }
            } else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task);
        }
    }複製程式碼

這個方法的邏輯比較複雜,這裡擷取了和taskAffinity相關的部分。註釋1處遍歷mTaskHistory列表,列表的元素為TaskRecord,
用於儲存沒有被銷燬的Task。註釋2處得到某一個Task的資訊。註釋3處將Task的rootAffinity(初始的taskAffinity)和目標Activity的taskAffinity做對比,如果相同,則將FindTaskResult的matchedByRootAffinity 屬性設定為true,說明找到了匹配的Task。

參考資料
《深入理解Android卷二》
《深入理解Android核心設計思想》第二版
《Android開發藝術探索》
ActivityRecord、TaskRecord、ActivityStack


歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。

相關文章