扒扒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,那么启动模式会发生什么变化?
image

下面我就带着大家一块测试和分析一下,本篇博客测试设备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关系,关于三者相信现在应该有大致了解了。

image

taskAffinity属性

搞不清任务栈Task?想要自己管理Task?想要自己指定Activity进入哪个Task?再来扒扒taskAffinity。任务栈Task在上面TaskRecord中已经介绍,就是用来保存Activity信息,管理Activity打开顺序的,一些列相同的Activity会放进同一个任务栈Task。
taskAffinity词如其名,关联Task,即我们可指定Activity关联到哪个Task,每个Activity都有taskAffinity属性,它是一个字符串类型,注意:我们也可以在同一个Task中放置不同应用的Activity,通常如果Activity没有显示的指明这个属性,那么它会继承<application>中的taskAffinity属性,如果<application>中也未指明该属性,那么采用的是<manifest>元素所设置的软件包名称,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,那么它们都有什么特点呢,话不多说,直接上图:

image

以下也有一张官方的启动模式图可供参考,毕竟原汁原味的看着更加靠谱:

image

standard

都知道此模式是Activity默认的启动模式,如果你不设置launchMode,那么就会默认采取此方式,如果硬要说有啥缺陷的话就是资源浪费,因为每次打开一个页面都会重新创建该界面的实例,然后放到任务栈中,不管任务栈中是否有该Activity。

image
标准模式(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的多个实例可能会以任务的形式显示在概览屏幕中。 它有四个属性值,看下下面的图表:

image

现在我们从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属性值要视情况而定。再来看一下概览屏幕演示:

ezgif.com-resize.gif

发现概览屏幕有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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容