天天看點

扒扒Task與Activity啟動模式

最近在重新整理Activity的啟動模式,順便也扒了扒任務棧Task,接着又去了解了下Android的概覽螢幕,把頁面間的跳轉、任務棧存放與管理及從任務清單視窗恢複,整體串通的了解了一下。下面有幾個基本的問題,你不妨測試一下,看看能掌握多少?

發自靈魂的拷問

  • 設定啟動模式為singleTask,若棧内已有該執行個體,是否棧内就一定是複用的,不會建立執行個體?
  • 若Intent設定FLAG_ACTIVITY_NEW_TASK,任何啟動模式,如果采取startActivityForResult()啟動Activity,onActivityResult()有何變化?
  • LauncherActivity->A(standard)->B(singleInstance)按下home鍵,點選桌面app圖示,會發生什麼?
  • LauncherActivity->A(standard)->B(standard)->C(singleInstance)->A,按下傳回鍵,會發生什麼?
  • 當調用startActivityForResult啟動Activity,那麼啟動模式會發生什麼變化?
扒扒Task與Activity啟動模式

下面我就帶着大家一塊測試和分析一下,本篇部落格測試裝置Pixel(API19)、小米(API21)和OPPO(API27),主要是把android系統5.0作為一個分水嶺來測試,因為官方的文檔很多地方未交代清楚,需要考證。 ## ActivityRecord、TaskRecord和ActivityStack 先來扒扒這三者的關系,可以更加友善的讓我們去了解啟動模式。 `ActivityRecord`可以說是在任務棧中記錄或儲存Activity資訊的實體類,它對應一個Activity,但是同一個Activity可以對應多個ActivityRecord,因為Activity可以多次被啟動執行個體化,由啟動模式、taskAffinity和FLAG決定的。

/** 【注意:此處源碼的解釋】
 * An entry in the history stack, representing an activity.
 */
final class ActivityRecord extends ConfigurationContainer implements AppWindowContainerListener {
  ApplicationInfo appInfo; // information about activity's app
    final int launchedFromPid; // always the pid who started the activity.
    final int launchedFromUid; // always the uid who started the activity.
    final String launchedFromPackage; // always the package who started the activity.
    final int userId;          // Which user is this running for?
    final Intent intent;    // the original intent that generated us
    final ComponentName realActivity;  // the intent component, or target of an alias.
    final String shortComponentName; // the short component name of the intent
    final String resolvedType; // as per original caller;
    final String packageName; // the package implementing intent's component
    final String processName; // process where this component wants to run
    final String taskAffinity; // as per ActivityInfo.taskAffinity
   ......部分省略
}
           

TaskRecord表示任務棧,記錄着Activity開啟的先後順序,特點是先進後出,一系列相同的ActivityRecord儲存在一個TaskRecord中,他們的TaskId是相同的,也就是說TaskRecord是由一個或者多個ActivityRecord所組成,簡單看下字段

class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener {
    final int taskId;       // Unique identifier for this task.
    String affinity;        // The affinity name for this task, or null; may change identity.
    String rootAffinity;    // Initial base affinity, or null; does not change from initial root.
    final IVoiceInteractionSession voiceSession;    // Voice interaction session driving task
    final IVoiceInteractor voiceInteractor;         // Associated interactor to provide to app
    Intent intent;          // The original intent that started the task. Note that this value can
                            // be null.
    Intent affinityIntent;  // Intent of affinity-moved activity that started this task.
    int effectiveUid;       // The current effective uid of the identity of this task.
    ComponentName origActivity; // The non-alias activity component of the intent.
    ComponentName realActivity; // The actual activity component that started the task.
    boolean realActivitySuspended; // True if the actual activity component that started the
                                   // task is suspended.
    boolean inRecents;      // Actually in the recents list?
    long lastActiveTime;    // Last time this task was active in the current device session,
    ......部分省略
}
           

ActivityStack則是用來管理TaskRecord的,裡面包含多個TaskRecord,用源碼的解釋就是一個棧所有活動界面(TaskRecord中)的狀态和管理

/**【注意:此處源碼的解釋】
 * State and management of a single stack of activities.
 */
class ActivityStack<T extends StackWindowController> extends ConfigurationContainer implements StackWindowListener {
 enum ActivityState {
        INITIALIZING,
        RESUMED,
        PAUSING,
        PAUSED,
        STOPPING,
        STOPPED,
        FINISHING,
        DESTROYING,
        DESTROYED
    }
    ......部分省略
 /**
     * The back history of all previous (and possibly still
     * running) activities.  It contains #TaskRecord objects.
     */
    private final ArrayList<TaskRecord> mTaskHistory = new ArrayList<>();
    /**
     * List of running activities, sorted by recent usage.
     * The first entry in the list is the least recently used.
     * It contains HistoryRecord objects.
     */
    final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>();
    /**
     * When we are in the process of pausing an activity, before starting the
     * next one, this variable holds the activity that is currently being paused.
     */
    ActivityRecord mPausingActivity = null;
    /**
     * This is the last activity that we put into the paused state.  This is
     * used to determine if we need to do an activity transition while sleeping,
     * when we normally hold the top activity paused.
     */
    ActivityRecord mLastPausedActivity = null;
    ......部分省略
}
           

簡單的舉個栗子:從系統桌面開始,現在有一個MainActivity(啟動頁)->A(standard)->B(singleTop)-C(singleTask)->D(singleInstance),看一下Task棧資訊是怎麼樣的。

adb指令:adb shell dumpsys activity activities
           
Running activities (most recent first):
 TaskRecord{f6628d2 #372 A=com.learn.hule.mylearn U=0 StackId=1 sz=1}
  Run #4: ActivityRecord{fcfe71f u0 com.learn.hule.mylearn/.launchermode.DActivity t372}
 TaskRecord{35b8da3 #371 A=com.learn.hule.mylearn U=0 StackId=1 sz=4}
  Run #3: ActivityRecord{2ae9a3c u0 com.learn.hule.mylearn/.launchermode.CActivity t371}
  Run #2: ActivityRecord{a56bb55 u0 com.learn.hule.mylearn/.launchermode.BActivity t371}
  Run #1: ActivityRecord{f488dba u0 com.learn.hule.mylearn/.launchermode.AActivity t371}
  Run #0: ActivityRecord{d17c968 u0 com.learn.hule.mylearn/.MainActivity t371}
mResumedActivity: ActivityRecord{fcfe71f u0 com.learn.hule.mylearn/.launchermode.DActivity t372}
mLastPausedActivity: ActivityRecord{2ae9a3c u0 com.learn.hule.mylearn/.launchermode.CActivity t371}

Running activities (most recent first):
 TaskRecord{5f17938 #2 A=com.oppo.launcher U=0 StackId=0 sz=1}
  Run #0: ActivityRecord{97e6777 u0 com.oppo.launcher/.Launcher t2}
           

TaskRecord{f6628d2 #372 A=com.learn.hule.mylearn U=0 StackId=1 sz=1}

大括号裡分别代表的是:TaskRecord的hashCode、taskId、affinity、userId、stackId、

List<ActivityRecord>

的數量,來看一下源碼:

StringBuilder sb = new StringBuilder(128);
        if (stringName != null) {
            sb.append(stringName);
            sb.append(" U=");
            sb.append(userId);
            sb.append(" StackId=");
            sb.append(getStackId());
            sb.append(" sz=");
            sb.append(mActivities.size());
            sb.append('}');
            return sb.toString();
        }
        sb.append("TaskRecord{");
        sb.append(Integer.toHexString(System.identityHashCode(this)));
        sb.append(" #");
        sb.append(taskId);
        if (affinity != null) {
            sb.append(" A=");
            sb.append(affinity);
        } else if (intent != null) {
            sb.append(" I=");
            sb.append(intent.getComponent().flattenToShortString());
        } else if (affinityIntent != null && affinityIntent.getComponent() != null) {
            sb.append(" aI=");
            sb.append(affinityIntent.getComponent().flattenToShortString());
        } else {
            sb.append(" ??");
        }
        stringName = sb.toString();
        return toString();
           

ActivityRecord{2ae9a3c u0 com.learn.hule.mylearn/.launchermode.CActivity t371}

大括号裡分别代表的是:ActivityRecord的hashCode、userId、完整類名、taskId,下面是源碼:

if (stringName != null) {
            return stringName + " t" + (task == null ? INVALID_TASK_ID : task.taskId) +
                    (finishing ? " f}" : "}");
        }
        StringBuilder sb = new StringBuilder(128);
        sb.append("ActivityRecord{");
        sb.append(Integer.toHexString(System.identityHashCode(this)));
        sb.append(" u");
        sb.append(userId);
        sb.append(' ');
        sb.append(intent.getComponent().flattenToShortString());
        stringName = sb.toString();
        return toString();
           

我們用下面圖表來表示,也看下ActivityRecord、TaskRecord與ActivityStack關系,關于三者相信現在應該有大緻了解了。

扒扒Task與Activity啟動模式

## taskAffinity屬性 搞不清任務棧Task?想要自己管理Task?想要自己指定Activity進入哪個Task?再來扒扒taskAffinity。任務棧Task在上面TaskRecord中已經介紹,就是用來儲存Activity資訊,管理Activity打開順序的,一些列相同的Activity會放進同一個任務棧Task。 taskAffinity詞如其名,關聯Task,即我們可指定Activity關聯到哪個Task,每個Activity都有taskAffinity屬性,它是一個字元串類型,**注意:我們也可以在同一個Task中放置不同應用的Activity**,通常如果Activity沒有顯示的指明這個屬性,那麼它會繼承``中的taskAffinity屬性,如果``中也未指明該屬性,那麼采用的是``元素所設定的軟體包名稱,Task中也有affinity屬性,它為根Activity的taskAffinity屬性。 還是從MainActivity(啟動頁)->A(standard)->B(singleTop)-C(singleTask)->D(singleInstance),這回我們分别設定taskAffinity為包名+(TaskA、TaskB、TaskC、TaskD),看看Task變化: ``` Running activities (most recent first): TaskRecord{7931eb9 #27 A=com.learn.hule.mylearn.TaskD U=0 StackId=1 sz=1} Run #4: ActivityRecord{b9604f0 u0 com.learn.hule.mylearn/.launchermode.DActivity t27} TaskRecord{916a2fe #26 A=com.learn.hule.mylearn.TaskC U=0 StackId=1 sz=1} Run #3: ActivityRecord{53673bc u0 com.learn.hule.mylearn/.launchermode.CActivity t26} TaskRecord{5c05ed7 #25 A=com.learn.hule.mylearn U=0 StackId=1 sz=3} Run #2: ActivityRecord{c8d76d5 u0 com.learn.hule.mylearn/.launchermode.BActivity t25} Run #1: ActivityRecord{36f033a u0 com.learn.hule.mylearn/.launchermode.AActivity t25} Run #0: ActivityRecord{23f6498 u0 com.learn.hule.mylearn/.MainActivity t25} ``` 可以看到隻有singleTask模式下配合taskAffinity才會開啟新的Task。其他模式隻是taskAffinity的值變了,并未啟動新的Task,隻是關聯到某個Task。但是,如果我們在A啟動B的時候如果設定`Intent.FLAG_ACTIVITY_NEW_TASK`看看有什麼變化

Running activities (most recent first):
 TaskRecord{309dcd2 #36 A=com.learn.hule.mylearn.TaskD U=0 StackId=1 sz=1}
  Run #4: ActivityRecord{93395c3 u0 com.learn.hule.mylearn/.launchermode.DActivity t36}
TaskRecord{8e731a3 #35 A=com.learn.hule.mylearn.TaskC U=0 StackId=1 sz=1}
  Run #3: ActivityRecord{8b8c13f u0 com.learn.hule.mylearn/.launchermode.CActivity t35}
 TaskRecord{2a1fca0 #34 A=com.learn.hule.mylearn.TaskB U=0 StackId=1 sz=1}
  Run #2: ActivityRecord{8bdda7b u0 com.learn.hule.mylearn/.launchermode.BActivity t34}
 TaskRecord{fa0d611 #33 A=com.learn.hule.mylearn U=0 StackId=1 sz=2}
  Run #1: ActivityRecord{601d68 u0 com.learn.hule.mylearn/.launchermode.AActivity t33}
  Run #0: ActivityRecord{e6abae7 u0 com.learn.hule.mylearn/.MainActivity t33}
           

這時候發現BActivity也開啟了一個新的Task,經過測試得出:

  • taskAffinity在啟動模式為singleTask時才能開啟新的Task
  • taskAffinity在Activity的啟動模式為standard與singleTop下手動結合

    Intent.FLAG_ACTIVITY_NEW_TASK

    才能開啟新的Task
  • taskAffinity配合

    allowTaskReparenting

    屬性可以更換所屬的Task(不同應用)

Activity啟動模式

最後,再來扒扒重要的角色,大家都知道Activity的啟動模式分别是standard、singleTask、singleTop、singleInstance,那麼它們都有什麼特點呢,話不多說,直接上圖:

扒扒Task與Activity啟動模式

以下也有一張官方的啟動模式圖可供參考,畢竟原汁原味的看着更加靠譜:

扒扒Task與Activity啟動模式

) ### standard 都知道此模式是Activity預設的啟動模式,如果你不設定launchMode,那麼就會預設采取此方式,如果硬要說有啥缺陷的話就是資源浪費,因為每次打開一個頁面都會重新建立該界面的執行個體,然後放到任務棧中,不管任務棧中是否有該Activity。

扒扒Task與Activity啟動模式

) ``` 标準模式(standard)采用startActivity()啟動Activity com.learn.hule.mylearn D/MainActivity: ===: onPause: com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=79 hashCode=160913568 com.learn.hule.mylearn D/MainActivity: ===: onStop: com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=79 hashCode=241026452 com.learn.hule.mylearn D/CActivity: ===onCreate() TaskId=79 hashCode=245921096 com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=79 hashCode=104278959 ``` 采用startActivity啟動Activity的話,從MainActivity(啟動頁)->A->B->C->A,會發現他們的TaskId都是一樣的,證明在同一個任務戰中,但是當從棧頂C再次跳轉到A時,發現棧中有2個A,但是2個A的hashCode是不同的,證明重新建立了A的執行個體。

标準模式(standard)startActivityForResult()啟動Activity
com.learn.hule.mylearn D/MainActivity:===: onPause: 
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=151 hashCode=13826343
com.learn.hule.mylearn D/MainActivity:===: onStop: 
com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=151 hashCode=177180538
com.learn.hule.mylearn D/CActivity: ===onCreate() TaskId=151 hashCode=24401097
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=151 hashCode=56917700
按下傳回鍵
com.learn.hule.mylearn D/CActivity: ===onActivityResult() TaskId=151 hashCode=24401097
com.learn.hule.mylearn D/BActivity: ===onActivityResult() TaskId=151 hashCode=177180538
com.learn.hule.mylearn D/AActivity: ===onActivityResult() TaskId=151 hashCode=13826343
com.learn.hule.mylearn D/MainActivity===: onDestroy: 
           

采用startActivityForResult啟動Activity的話,從MainActivity(啟動頁)->A->B->C->A,發現跟startActivity是一樣的。當按下傳回鍵的時候,發現都是正常的回調,經過測試發現:

  • standard啟動模式不會受到不同API版本與啟動方法影響
  • standard模式下預設的Task為啟動它的Activity所在的Task
  • standard模式下可以通過taskAffinity結合

    Intent.FLAG_ACTIVITY_NEW_TASK

    可開啟新的Task

singleTop

如果目标Task的 Activity 堆棧頂部已有一個Activity執行個體,則該執行個體會(通過調用 onNewIntent())接收新的Intent;此時不會建立新執行個體。若堆棧頂部沒有Activity執行個體,系統會建立新執行個體并将其送入堆棧。

官方将“standard”和“singleTop”啟動模式歸為為一類,因為這兩種啟動模式可多次進行執行個體化。執行個體也可以屬于任何Task,并且可位于Activity堆棧中的任何位置。通常該執行個體位于調用startActivity() 的Task中,但是如果Intent對象設定FLAG為

FLAG_ACTIVITY_NEW_TASK

,結合taskAffinity屬性,在此情況下可選擇其他Task。

A(standard)->B(singleTop)->B(singleTop) 
startActivity()啟動
om.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=53 hashCode=251544623
com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=53 hashCode=37167906
com.learn.hule.mylearn D/BActivity: ===onNewIntent() TaskId=53 hashCode=37167906
startActivityForResult()啟動
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=52 hashCode=251544623
com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=52 hashCode=37167906
com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=52 hashCode=69426104
com.learn.hule.mylearn D/BActivity: ===onActivityResult() TaskId=52 hashCode=37167906
com.learn.hule.mylearn D/AActivity: ===onActivityResult() TaskId=52 hashCode=251544623
           

可以看到用startActivity()啟動Activity時,如果執行個體已經在棧頂,則直接調用onNewIntent(),此時不會在建立執行個體,而且TaksId與啟動它的A是保持一緻的,證明在同一個Task中。

當采用startActivityForResult()啟動時,雖然在同一個Task中,但是并未調用onNewIntent(),而是重新建立了執行個體,他們的hashCode都不同。

  • 棧頂複用在startActivityForResult()啟動會失效
  • singleTop模式用taskAffinity結合Intent.FLAG_ACTIVITY_NEW_TASK可開啟新的Task

singleTask

系統會在新Task的根位置建立 Activity 并向其發送 Intent。不過,如果已存在 Activity 執行個體,則系統會調用該執行個體的 onNewIntent() 方法(而非建立新的 Activity 執行個體),并向其發送 Intent。

The system creates the activity at the root of a new task and routes the intent to it. However, if an instance of the activity already exists, the system routes the intent to existing instance through a call to its onNewIntent() method, rather than creating a new one.

這個官方的描述可能有點含糊,因為經過測試發現,隻有設定了taskAffinity屬性才會為對應啟動的 Activity建立一個新的Task。它才能成為新Task的根,後續的Activity才會進入這個Task中。

A(standard)->B(singleTop)->C(singleTask)->D(singleInstance)->C
startActivity()啟動
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=54 hashCode=251544623
com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=54 hashCode=37167906
com.learn.hule.mylearn D/CActivity: ===onCreate() TaskId=54 hashCode=70721783
com.learn.hule.mylearn D/DActivity: ===onCreate() TaskId=55 hashCode=215960458
com.learn.hule.mylearn D/CActivity: ===onNewIntent() TaskId=54 hashCode=70721783
startActivityForResult()啟動,并按下傳回【Android版本5.0】
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=45 hashCode=247150306
com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=45 hashCode=194419894
com.learn.hule.mylearn D/CActivity: ===onCreate() TaskId=45 hashCode=147679050
com.learn.hule.mylearn D/DActivity: ===onCreate() TaskId=45 hashCode=86649502
com.learn.hule.mylearn D/CActivity: ===onCreate() TaskId=45 hashCode=239219901
com.learn.hule.mylearn D/DActivity: ===onActivityResult() TaskId=45 hashCode=86649502
com.learn.hule.mylearn D/CActivity: ===onActivityResult() TaskId=45 hashCode=147679050
com.learn.hule.mylearn D/BActivity: ===onActivityResult() TaskId=45 hashCode=194419894
com.learn.hule.mylearn D/AActivity: ===onActivityResult() TaskId=45 hashCode=247150306
startActivityForResult()啟動,并按下傳回【Android版本4.4】
om.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=4 hashCode=-1660848312
com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=4 hashCode=-1660442544
com.learn.hule.mylearn D/BActivity: ===onActivityResult() TaskId=4 hashCode=-1660442544
com.learn.hule.mylearn D/CActivity: ===onCreate() TaskId=4 hashCode=-1660865488
com.learn.hule.mylearn D/CActivity: ===onActivityResult() TaskId=4 hashCode=-1660865488
com.learn.hule.mylearn D/DActivity: ===onCreate() TaskId=5 hashCode=-1660343352
com.learn.hule.mylearn D/DActivity: ===onActivityResult() TaskId=5 hashCode=-1660343352
com.learn.hule.mylearn D/CActivity: ===onNewIntent() TaskId=4 hashCode=-1660865488
com.learn.hule.mylearn D/AActivity: ===onActivityResult() TaskId=8 hashCode=-1660835216
           

可以看到預設情況下singleTask并不會開啟新的Task,比較有意思的是如果用startActivityForResult()啟動Activity,在Android系統版本5.0以上,CActivity擁有多個執行個體,他們的hashCode不同,但是在Android系統版本為4.4時候,它又是複用的,直接回調了onNewIntent()。

  • 棧内複用在startActivityForResult()啟動會失效
  • singleTask模式預設不會開啟新的Task,隻有設定了taskAffinity屬性才會
  • 版本5.0(API21)以下,如果采取startActivityForResult()啟動Activity,onActivityResult()回調是在啟動時調用的(猜測是系統bug),并不是在頁面傳回時調用

singleInstance

官方将singleInstance與singleTask歸為一類,這個模式就有點霸道了,它與singleTask不同的是:不允許其他Activity成為其Task的一部分。它是Task中唯一的Activity。如果它啟動另一個Activity,則系統會将該Activity配置設定給其他Task,就如同 Intent 中包含 FLAG_ACTIVITY_NEW_TASK 一樣.

A(standard)->B(singleTop)->C(singleTask)->D(singleInstance)->A
startActivity()啟動
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=10 hashCode=-1660820808
com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=10 hashCode=-1660440528
com.learn.hule.mylearn D/CActivity: ===onCreate() TaskId=10 hashCode=-1660322048
com.learn.hule.mylearn D/DActivity: ===onCreate() TaskId=11 hashCode=-1660837120
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=10 hashCode=-1660249512
startActivityForResult()啟動,按下傳回【4.4版本】
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=20 hashCode=-1660524688
com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=20 hashCode=-1660400616
com.learn.hule.mylearn D/BActivity: ===onActivityResult() TaskId=20 hashCode=-1660400616
com.learn.hule.mylearn D/CActivity: ===onCreate() TaskId=20 hashCode=-1660548408
com.learn.hule.mylearn D/CActivity: ===onActivityResult() TaskId=20 hashCode=-1660548408
com.learn.hule.mylearn D/DActivity: ===onCreate() TaskId=21 hashCode=-1660295280
com.learn.hule.mylearn D/DActivity: ===onActivityResult() TaskId=21 hashCode=-1660295280
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=20 hashCode=-1660176880
startActivityForResult()啟動,按下傳回【5.0版本】
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=73 hashCode=247150306
com.learn.hule.mylearn D/BActivity: ===onCreate() TaskId=73 hashCode=194419894
com.learn.hule.mylearn D/CActivity: ===onCreate() TaskId=73 hashCode=147679050
com.learn.hule.mylearn D/DActivity: ===onCreate() TaskId=73 hashCode=86649502
com.learn.hule.mylearn D/AActivity: ===onCreate() TaskId=73 hashCode=239219901
com.learn.hule.mylearn D/DActivity: ===onActivityResult() TaskId=73 hashCode=86649502
           

可以看到startActivity()和startActivityForResult()方法啟動DActivity執行個體都會開啟新的Task,但是,startActivityForResult()必須系統版本在5.0以下,才會開啟新的Task,當在再從D->A時,系統會将A重新配置設定到預設的Task。

  • 版本5.0(API21)以下,如果采取startActivityForResult()啟動Activity,onActivityResult()回調是在啟動時調用的(猜測是系統bug),并不是在頁面傳回時調用
  • 版本5.0(API21)以上,如果采取startActivityForResult()啟動Activity,并不會開啟新的Task。

綜上幾種啟動模式測試,不同的系統API與啟動方法可能對于開發者來說有不同的效果,以後在使用中需要多多留意:

  • taskAffinity結合Intent.FLAG_ACTIVITY_NEW_TASK,可開啟新的Task
  • singleTask模式預設不會開啟新的Task,隻有設定了taskAffinity屬性才會
  • Android系統版本5.0以上,采用startActivityForResult()啟動Activity,大多數情況下啟動模式都會失效,成為預設的standard
  • Android系統版本5.0以下,一定要留意onActivityResult()調用的時機,除standard和singleTop模式互相跳轉,其他模式間跳轉是在啟動界面調用的,不是在傳回界面時調用的

不得不說的documentLaunchMode

最最後,再來扒扒概覽螢幕(最新動态螢幕、最近任務清單或最近使用的應用),documentLaunchMode指定每次啟動任務時,如何向其添加新的Activity執行個體,該屬性允許使用者讓多個來自同一應用的文檔出現在概覽螢幕中。使用者可以浏覽該清單并選擇要恢複的任務,也可以通過滑動清除任務将其從清單中移除。 對于 Android5.0版本(API級别21),documentLaunchMode不同,同一Activity的多個執行個體可能會以任務的形式顯示在概覽螢幕中。 它有四個屬性值,看下下面的圖表:

扒扒Task與Activity啟動模式

) 現在我們從MainActivity->AActivity(none)->BActivity(always)->CActivity(never)->DActivity(intoExisting),看一下Task是如何變化的

Running activities (most recent first):
 TaskRecord{abacfae #204 A=com.learn.hule.mylearn U=0 StackId=1 sz=1}
  Run #4: ActivityRecord{fa6e38b u0 com.learn.hule.mylearn/.launchermode.DActivity t204}
 TaskRecord{b65a14f #203 A=com.learn.hule.mylearn U=0 StackId=1 sz=2}
  Run #3: ActivityRecord{bba4538 u0 com.learn.hule.mylearn/.launchermode.CActivity t203}
  Run #2: ActivityRecord{9d66c84 u0 com.learn.hule.mylearn/.launchermode.BActivity t203}
 TaskRecord{385f0dc #202 A=com.learn.hule.mylearn U=0 StackId=1 sz=2}
  Run #1: ActivityRecord{f4ac654 u0 com.learn.hule.mylearn/.launchermode.AActivity t202}
  Run #0: ActivityRecord{85b4792 u0 com.learn.hule.mylearn/.MainActivity t202}
           

可以發現always屬性值是會新開Task的,none屬性值不設定FLAG為

FLAG_ACTIVITY_NEW_TASK

預設是不會新開Task的,never屬性值則是與啟動他的界面保持一緻,不會新開Task,而intoExisting屬性值要視情況而定。再來看一下概覽螢幕示範:

扒扒Task與Activity啟動模式

) 發現概覽螢幕有3個任務文檔,分别是AActivity、CActivity、DActivity,如果從概覽螢幕恢複CActivity的話,點選傳回,傳回到了BActivity,因為他們在同一個TaskRecord中,CActivity在棧頂,如果再點選傳回直接傳回到了主螢幕中,再點選app圖示時,此時啟動了AActivity,點選傳回,傳回到了MainActivity,接着傳回,直接傳回到了桌面,因為點選圖示時,預設啟動的是MainActivity所在的任務棧,他與AActivity在同一個TaskRecord中,屬于一些列相同的ActivityRecord。 ## 測試答案

1.設定啟動模式為singleTask,若棧内已有該執行個體,不一定能複用棧内執行個體

  • 該執行個體之前雖然存在,但是已經被系統回收
  • Android系統5.0以上,采用startActivityForResult()啟動,則singleTask模式失效,是會重新建立執行個體

2.若Intent設定

FLAG_ACTIVITY_NEW_TASK

,任何啟動模式,onActivityResult()有何變化

  • 不同的Android系統版本onActivityResult()回調都會在啟動界面的時候執行,傳回該界面則無效

3.LauncherActivity->A(standard)->B(singleInstance)按下home鍵,點選桌面app圖示,不會直接啟動B界面

  • 按下Home鍵,此時目前B為前台棧,而A所在的任務棧被壓倒背景,點選app圖示,預設啟動的是LauncherActivity所在的任務棧,即A所在的棧被拉到前台,故會喚醒LauncherActivity所在TaskRecord的棧頂,是以是A界面

4.LauncherActivity->A(standard)->B(standard)->C(singleInstance)->A,按下傳回鍵,會發生什麼?

  • 因為預設不設定taskAffinity屬性的情況下,存在2個TaskRecord,處在前台Task中依次從棧底到棧頂的是

    ActivityRecord<Launcher>

    ActivityRecord<A>

    ActivityRecord<B>

    ActivityRecord<A>

    ,他們都是一系列相同的ActivityRecord,而壓在背景的Task為

    ActivityRecord<C>

    所在的Task,故按傳回鍵,是從前台Task棧頂A依次傳回B->A->LauncherActivity,并不會傳回到C。

5.當調用startActivityForResult啟動Activity,啟動模式會發生什麼變化

  • Android系統大于5.0時,啟動模式失效,成為預設的standard