0

深入理解 Activity 的四种启动模式

Jenny 2月11日 发布于Android yifeng.studio

Activity 作为 Android 最具创意的概念之一,在内存管理方面拥有设计良好的架构,能够让多任务在这个最流行的手机操作系统中完美地工作。

深入理解 Activity 的四种启动模式

2月11日 发布,来源:yifeng.studio

翻译:亦枫
原文:Understand Android Activity’s launchMode: standard, singleTop, singleTask and singleInstance

Activity 作为 Android 最具创意的概念之一,在内存管理方面拥有设计良好的架构,能够让多任务在这个最流行的手机操作系统中完美地工作。

然而,Activity 不仅仅是在屏幕上被启动这么简单,这与它的启动方式有关。这个话题里面有很多细节知识。其中,最重要的一个就是启动模式(launchMode),也是我们将要在这篇文章中讨论的内容。

每一个 Activity 都被用于不同的目的而被创建。一些被设计为通过单个 Intent 发送而启动、分开独立地工作,比如邮件客户端的邮件撰写页面;而一些被设计为一个单例式地工作,如收件箱页面。

这就是为什么指定 Activity 被创建为一个新的实例还是使用已经存在的实例非常有必要的原因。它可能会导致一个很差的用户体验或功能性问题。感谢 Android 系统工程师,让我们能够在专用于解决这个问题的 launchMode 的帮助下简单实现这些功能。

指定启动模式

通常我们能够直接在 AndroidManifest.xml 文件里面的 <activity> 标签中使用一个属性指定启动模式,如:

1
2
3
4
<activity
android:name=".SingleTaskActivity"
android:label="singleTask launchMode"
android:launchMode="singleTask">

一共有四种类型的启动模式。让我们逐一分析。

standard

这也是默认模式。

这种模式下的 Activity 表现为,每一个 intent 发送时永远创建一个新的 Activity 来独立工作。如果发送 10 个 Intents 来撰写邮件,应该有 10 个 Activities 被启动来独立服务每一个 Intent。因此,在一个设备中这种类型的 Activity 的数量是没有限制的。

在 Android 5.0 以前版本的表现

这种类型的 Activity 被创建并放置在发送 Intent 的相同 Task 的栈顶。

下面这张图展示出当我们分享一张图片到一个 standard 模式的 Activity 时发生的状况。它将如描述那样被堆放在相同的 task 中,尽管它们来自不同的应用程序。

并且这是你在任务管理器中会看到的样子。(可能会有一点奇怪)

如果我们切换到另一个应用程序、然后再切换回 Gallery 应用的话,我们仍会看到 standard 启动模式的 Activity 放置在 Gallery 任务栈的顶部。所以,如果我们需要使用 Gallery 做别的事情,必须先结束掉这个额外 Activity 里面的工作。

在 Android 5.0 及以后版本的表现

如果这些 Activities 来自相同的应用,表现形式与 5.0 之前的系统一样,出现在 task 栈顶。

但是如果一个 Intent 被发送自不同的应用程序的话,新的 task 将被创建,同时新创建的 Activity 作为一个根 Activity 被放置,就像下面这样。

打开任务管理器的话,你看到的是这样。

发生这样的变化是因为任务管理系统在 Lollipop 开始被修改来使其变得更好也更有意义。在 Lollipop 设备上,你可以切换回 Gallery 应用,因为他们在不同的 Task 栈中。如果你再发送另一个 Intent 的话,一个新的 Task 将被创建来像前面创建的 task 一样工作。

这种类型 Activity 的使用案例常见如一个填写邮件的页面或者社交网络信息发布的页面。如果你认为一个 Activity 应该独立地工作来服务于一个独立发送的 Intent 的话,可以考虑使用 standard 模式。

singleTop

singleTop 模式的 Activity 表现形式与 standard 很像,多数情况下 Activity 实例会按照我们想要的样子那样被创建。唯一不同的是,如果在调用者 Task 栈顶已经存在有一个相同类型的 Activity 实例的话,将不会创建新的 Activity,取而代之的是通过 onNewIntent() 方法将 Intent 发送至这个已经存在的 Activity 实例中。

使用 singleTop 模式时,你必须同时在 onCreate()onNewIntent() 方法中接受处理发送而来的 Intent 数据,以应对所有使用场景。

这种模式的使用案例之一便是搜索功能。想象一下,创建一个能够跳转至一个展示搜索结果的 SearchActivity 的搜索框。为了拥有更好的用户体验,通常我们会在搜索结果页面也放置一个搜索框,使用户在不需要按返回键的情况下搜索其他关键字。

现在设想一下,如果我们每一次都启动一个新的 SearchActivity 来展示新的搜索结果,10 次搜索便产生 10 个新的 Activities。当你按返回键的时候,就会显得非常奇怪。因为你不得不按 10 次返回键来跳过这些搜索结果页面从而返回至你前面的 Activity。

相反的,如果在栈顶有一个 SearchActivity 的话,更好的做法是我们发送一个 Intent 到已经存在的 Activity 实例并让它更新搜索结果。现在只有一个 SearchActivity 位于栈顶,你只需要按一次返回键就能回到之前的 Activity。这显然更有意义。

然而,singleTop 模式仅仅在与调用者相同的 task 中起作用。如果你希望一个 Intent 被发送至另一个 Task 里面的已经存在的栈顶 Activity 的话,我不得不失望地告诉你这种方式并不奏效。启动 singleTop Activity 的 Intent 来自另一个应用程序时,一个新的 Activity 会被启动,就像 standard 模式那样(5.0 以前:放置在调用者 Task 的栈顶,5.0 开始:创建一个新的 Task)。

singleTask

这种模式与 standard 和 singleTop 截然不同。拥有 singleTask 启动模式的 Activity 在系统中只允许存在一个实例(有点像单例)。如果系统中已经有一个 Activity 实例的话,拥有该实例的 Task 将被移到顶部,并且 Intent 将通过 onNewIntent() 方法传递过来。如果没有,新的 Activity 将被创建并放在对应的 Task 中。

在相同应用程序中

如果系统中还没有 singleTask Activity 实例的话,创建一个新的 Activity 并放置在相同 Task 里面。

但是如果已经有一个的话,这个 singleTask Activity 上面的所有Activities 都会按照适当的生命周期方式自动销毁,来使得我们想要的 Activity 出现在栈顶。同时,这个 singleTask Activity 通过 onNewIntent() 方法接受被发送的 Intent 对象。

虽然用户体验不是那么良好但是它确实是这么被设计的…

你也许会在文档中会看到这部分内容:

The system creates a new task and instantiates the activity at the root of the new task.

但是根据实际经验,它并不是像描述这样工作的。根据 dumpsys activity 命令展示的结果,singleTask Activity 被放入调用者 Task 的栈顶。

1
2
3
4
5
6
7
8
9
Task id #239
TaskRecord{428efe30 #239 A=com.thecheesefactory.lab.launchmode U=0 sz=2}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }
Hist #1: ActivityRecord{429a88d0 u0 com.thecheesefactory.lab.launchmode/.SingleTaskActivity t239}
Intent { cmp=com.thecheesefactory.lab.launchmode/.SingleTaskActivity }
ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}
Hist #0: ActivityRecord{425fec98 u0 com.thecheesefactory.lab.launchmode/.StandardActivity t239}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }
ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}

如果你希望让 singleTask Activity 表现得像官方描述那样:创建一个新的 Task 并作为根 Activity 放置,你需要像下面这样额外指派 taskAffinity 属性给这个 Activity。

1
2
3
4
5
<activity
android:name=".SingleTaskActivity"
android:label="singleTask launchMode"
android:launchMode="singleTask"
android:taskAffinity="">

这种情况下,启动 SingleTaskActivity 时结果是这样的。

至于是否使用 taskAffinity 属性,取决于你想让你的 Activity 如何呈现。

在不同应用程序中

如果系统中还没有 singleTask Activity 实例的话,新的 Task 将被创建,并且新创建的 Activity 被作为根 Activity 放置。

如果已经存在一个 singleTask Activity 拥有者 Task 并且 Activity 还没有被创建的话,新创建的 Activity 将被置于栈顶。

如果一个 singleTask Activity 实例已经在某个 Task 中存在,整个 Task 将会出现在顶部并且位于该 singleTask Activity 上面的所有 Activity 会以适当的生命周期流程销毁掉。如果使用返回按钮,用户不得不先经历当前 Task 所有 Activities 然后返回至调用者 Task。

使用这种模式的一个案例是任何入口处 Activity,例如邮件客户端的收件箱页面或者社交应用的时间轴页面。这些 Activities 不需要被设计为超过一个实例,所以 singleTask 模式更为适合。不论如何,你都得恰当地使用这种模式。因为这种模式下,就像上面说的, Activities 会不经用户确认自动被销毁掉。

singleInstance

这种模式很接近 singleTask,只允许系统中存在一个 Activity 实例。不同之处在于拥有这个 Activity 的 Task 只能包含一个 Activity 实例,也就是这个 singleInstance 模式的 Activity。如果使用这种模式的 Activity 打开另一个 Activity,系统将自动创建一个新的 Task 来容纳新的 Activity。同样的,如果 singleInstance Activity 被调用,新的 Task 将被创建来存放这个 Activity。

然而实际结果有一点怪异。根据 dumpsys 提供的信息,系统中有两个 Tasks 但是只有一个最后移动到顶部的 Task 出现在任务管理器中。所以,尽管有一个 TAsk 仍在后台运行,但是我们不能将其切换至前台。一点意义也没有。

下面的图例展示出,在栈中已经有一些 Activities 时,调用 singleInstance Activity 时发生的状况。

但是任务管理器看到的是这样。

由于这个 Task 只能拥有一个 Activity,我们再也无法切换回 Task #1。唯一实现的方式是通过桌面 Launcher 重新打开应用,但是取而代之的是这个 singleInstance Task 将被隐藏于后台中。

针对这个问题有一些解决方案。就像我们使用 singleTask Activity 那样,简单地指派一个 taskAffinity 属性给 singleInstance Activity 来支持任务管理器的多 Tasks 模式。

1
2
3
4
5
<activity
android:name=".SingleInstanceActivity"
android:label="singleInstance launchMode"
android:launchMode="singleInstance"
android:taskAffinity="">

现在看上去更有意义了。

这种模式很少被使用到。一些真实的使用场景是一个用于 Launcher 的 Activity 或者是那种你 100% 确保只有一个 Activity 的应用程序。无论如何我都建议你不要使用这种模式,除非你真的需要。

Intent Flags

除了通过直接在 AndroidManifest.xml 文件中指定启动模式,我们也能通过 Intent Flags 来指派更多的行为,例如:

1
2
3
Intent intent = new Intent(StandardActivity.this, StandardActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);

这段代码将启动一个拥有 singleTop 启动模式条件的名为 StandardActivity。

有很多 Flags 可供你使用,更多信息可以在 Intent 中找到。

希望这篇文章对你有帮助。


65 浏览 收藏 报告 阅读模式
载入中...