深入一点点之Activity的启动模式

508 阅读5分钟

Android中Activity的启动模式算是Activity中最基础的知识点之一了,很多人(包括我)也就稍微记一下四种启动模式的属性,然后就这样了,到了用的时候也觉得没什么大问题(其实也是主要用的Standard比较多)。其实里面有一些内容还是值得我们一探究竟,当我们用多了或者有一些特殊用途的时候还是要深入了解一点点才能更好地使用。

  • "standard"(默认模式)

    默认。系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。

  • "singleTop"

    如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。 例如,假设任务的返回栈包含根 Activity A 以及 Activity B、C 和位于顶部的 D(堆栈是 A-B-C-D;D 位于顶部)。收到针对 D 类 Activity 的 Intent。如果 D 具有默认的 "standard" 启动模式,则会启动该类的新实例,且堆栈会变成 A-B-C-D-D。但是,如果 D 的启动模式是 "singleTop",则 D 的现有实例会通过 onNewIntent() 接收 Intent,因为它位于堆栈的顶部;而堆栈仍为 A-B-C-D。但是,如果收到针对 B 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 "singleTop" 也是如此。

    注:为某个 Activity 创建新实例时,用户可以按“返回”按钮返回到前一个 Activity。 但是,当 Activity 的现有实例处理新 Intent 时,则在新 Intent 到达 onNewIntent() 之前,用户无法按“返回”按钮返回到 Activity 的状态。

  • "singleTask"

    系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。

    注:尽管 Activity 在新任务中启动,但是用户按“返回”按钮仍会返回到前一个 Activity。

  • "singleInstance"

    与 "singleTask" 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。

    我们再来看另一示例,Android 浏览器应用声明网络浏览器 Activity 应始终在其自己的任务中打开(通过在 <activity>元素中指定 singleTask 启动模式)。这意味着,如果您的应用发出打开 Android 浏览器的 Intent,则其 Activity 与您的应用位于不同的任务中。相反,系统会为浏览器启动新任务,或者如果浏览器已有任务正在后台运行,则会将该任务上移一层以处理新 Intent。 无论 Activity 是在新任务中启动,还是在与启动 Activity 相同的任务中启动,用户按“返回”按钮始终会转到前一个 Activity。 但是,如果启动指定 singleTask 启动模式的 Activity,则当某后台任务中存在该 Activity 的实例时,整个任务都会转移到前台。此时,返回栈包括上移到堆栈顶部的任务中的所有 Activity。 图 4 显示了这种情况。

上图显示如何将启动模式为“singleTask”的 Activity 添加到返回栈。 如果 Activity 已经是某个拥有自己的返回栈的后台任务的一部分,则整个返回栈也会上移到当前任务的顶部。

上面是从Android Developers搬运过来的,包括四种启动模式的基本描述和一个关于singleTask的例子。看完其实就能对启动模式有了基本的了解但有个关键点是我们常说的Activity栈其实就是官方文档中的任务,每个任务对应的是一个Activity栈。不过本文可是要深入一点点,实践是检验真理的唯一标准,接下来是用demo来检验上面对四种模式的说明。

检验很简单,就是各种不同启动模式的Activity相互启动并且打印出app中Activity的Task的情况。主要有一个问题,singleTask模式的Activity并没有在新的Task中启动

其实我们在清单文件声明Activity时还有一个很重要的属性android:taskAffinity=""。这个属性在清单文件的<Activity><Application>标签下都有,在Activity标签中这是就是它的Task的标识,在Application中就是Activity的默认Task的标识,如果我们没有自定义,则应用中的默认标识为应用的包名(清单文件根标签中的package属性)。我们上面启动SingleTask的Activity时它没有在新Task中启动的原因就是由于SingleTask会启动到标识为taskAffinity的Task中,而我们默认下所有Activity的taskAffinity就是包名,因此都启动到同一个默认Task中了。只要我们给SingleTask添加一个不同于包名的taskAffinity,则启动时就会启动到新的Task中。

另外还发现在singleInstance启动其它Activity时,均会跳到对应的TaskAffinity中,其他三种类型则会与父Activity在同一个Task中。这里的原因与Activity的跳转过程相关,这里暂时留着下次研究Activity的跳转、启动时再研究。

最后再说一个尚未解决的问题。

ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.AppTask appTask : manager.getAppTasks()) {}

我用以上代码遍历应用中的Task时,当Task数量大于2,默认的Task就不会被遍历出来。这个问题实在很奇怪。