launchMode
基本概念-Task
任务是用户在执行某项工作时与之互动的一系列 Activity 的集合。这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中。堆栈中的 Activity 永远不会重新排列,只会被送入和退出,在当前 Activity 启动时被送入堆栈,在用户使用返回按钮离开时从堆栈中退出。因此,返回堆栈按照“后进先出”的对象结构运作。
当用户点击桌面上某个App的图标时,该App的入口Activity会被启动,并放入系统为这个App创建的Task里,这个入口Activity必须有MAIN和LAUNCHER这两个intent过滤器。
<activity ... >
<intent-filter ... >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>
当按返回键退出App回到主菜单,这时查看最近任务,依然能看到刚刚被关闭的App残影,但这个Task已经不存在了,这样做是Android系统出于对用户的使用体验考虑。
为什么需要launchMode
移动应用体验与桌面体验的不同之处在于,用户与应用的互动并不总是在同一位置开始,而是经常以不确定的方式开始。例如,如果从主屏幕打开电子邮件应用,可能会看到电子邮件列表,如果通过社交媒体应用启动电子邮件应用,则可能会直接进入电子邮件应用的邮件撰写界面。Activity类的目的就是促进这种范式的实现。当一个应用调用另一个应用时,调用方应用会调用另一个应用中的 Activity,而不是整个应用。Activity 经常会启动属于其他应用的 Activity,因此需要launchMode来控制这种启动行为,例如其应该放在哪个Task等。
另外的一个原因是Activity的默认启动模式(standard模式)是每个Activity实例可以属于不同的Task,一个Task可以拥有多个实例。因此当想打破这种行为时,例如希望调用它的一个现有实例而不是在返回堆栈顶部创建一个新实例,或者希望在用户离开任务时清除返回堆栈中除根 Activity 以外的所有 Activity,那么就需要配置launcheModel来指定这种行为。
4种launchMode
-
singleTask
<activity android:name=".MailActivity" android:launchMode="singleTask">
singleTask
可以在Activity被其他App启动时,不会进入启动它的Task里,而是在属于它自己的Task里创建,并放在自己的栈顶,并且这个Task栈会压在启动它的Task上。当后台任务中已存在该 Activity 的实例,则系统会将该后台任务整个转到前台运行。如果这个后台任务中已存在该 Activity 的实例,并且这个Activity上还有其他Activity压着,那么系统将弹出这个Activity上所有其他Activity,使其出现在栈顶。这时按下最近任务键或者回到桌面键,那么这两个Task将进入后台,然后被拆分开。这时再次进入第一个App时,看到的Activity将不是启动的那个Activity,因为被启动的Activity已经进入它自己的Task里了。
singleTask
保证了只有一个Task里有这个Activity,并且这个Task里最多只有一个这个Activity实例,限制这个Activity全局只有一个实例对象。 -
singleInstance
<activity android:name=".MailActivity" android:launchMode="singleInstance">
与
singleTask
相似,唯一不同的是系统不会将任何其他 Activity 启动到包含该实例的任务中。该 Activity 始终是其任务唯一的成员;由该 Activity 启动的任何 Activity 都会在其他的任务中打开,这个Activity独占一个Task。例如,邮件App已经在后台任务中,当短信App启动邮件App的撰写邮件的Activity(singleInstance)时,那么即使邮件Task已经在后台任务中,但系统不会复用这个Task,而是依然创建一个新的邮件Task来存放撰写邮件的Activity。这时在启动的撰写邮件的Activity中打开新的Activity,那么由于独占性,这个Activity不会进入当前Task。
当点击最近任务时,后台只显示一个邮件Task的预览,并不会出现两个邮件Task预览,但这并不表示系统杀死了其中一个邮件Task,只是隐藏起来了,原因是
taskAffinity
冲突了,当多个Task有相同的taskAffinity
时,系统只会在最近任务显示一个Task,并且是最新打开的那个。 -
standard
-
默认值。系统在启动该 Activity 的任务中创建 Activity 的新实例,并将 intent 传送给该实例。
-
Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例。
-
-
singleTop
如果当前任务的顶部已存在 Activity 的实例,则系统会通过调用其
onNewIntent()
方法来将 intent 转送给该实例,而不是创建 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
android:name="com.my.MyActivity"
android:taskAffinity="string">
-
概念
-
“亲和性”表示 Activity 倾向于属于哪个任务。
-
默认情况下,同一应用中的所有 Activity 彼此具有亲和性。因此,在默认情况下,同一应用中的所有 Activity 都倾向于位于同一任务。
-
系统使用
manifest
元素中声明的软件包名称,标识应用的默认任务亲和性。
-
-
taskAffinity与launchMode的关系
- 当初次启动一个App时,系统会创建一个Task,这个Task会得到一个
taskAffinity
,这个taskAffinity
的值就是第一个Activity的taskAffinity
。 - 再启动新的Activity,如果将要被启动的Activity没有配置
taskAffinity
,那么直接进入当前Task栈。 - 如果将要被启动的Activity设置了
launchMode="singleTask"
,系统会先比对Activity和当前的Task的taskAffinity
是否相同:如果相同,正常入栈;如果不同,Activity会去寻找和它的taskAffinity
相同的Task入栈,找不到系统就为它创建一个新的Task。 - 因此,即使是同一个App,如果该App的一个Activity配置了
taskAffinity
,那么它也会被放在另一个Task中。
- 当初次启动一个App时,系统会创建一个Task,这个Task会得到一个
-
allowTaskReparenting
<activity android:name="com.my.MyActivity" android:allowTaskReparenting="true">
在这种情况下,一旦和 Activity 有相同亲和性的任务进入前台运行,Activity 就可从其启动的任务转移到该任务。
举例来说,假设一款旅行应用中定义了一个报告特定城市天气状况的 Activity。该 Activity 与同一应用中的其他 Activity 具有相同的亲和性(默认应用亲和性),并通过此属性支持重新归属。当您的某个 Activity 启动该天气预报 Activity 时,该天气预报 Activity 最初会和您的 Activity 同属于一个任务。不过,当旅行应用的任务进入前台运行时,该天气预报 Activity 就会被重新分配给该任务并显示在其中。这时再打开旅行应用,那么栈顶将不是报告特定城市天气状况的 Activity,因为这个Activity已经分配到属于它的天气预报Task中了。
启动模式的方式
-
在Manifest.xml清单文件中配置。
-
使用Intent标记,在调用startActivity()时,可以在Intent中添加标记。
如果 Activity A 启动 Activity B,Activity B 可在其清单中定义如何与当前任务相关联(如果关联的话),Activity A 也可以请求 Activity B 应该如何与当前任务关联。如果两个 Activity 都定义了 Activity B 应如何与任务关联,将优先遵循 Activity A 的请求(在 intent 中定义),而不是 Activity B 的请求(在清单中定义)。