Kevin Su bio photo

Kevin Su

patience, persevere, and enjoy

Email Github Stackoverflow

一个Application由n个Activity构成(当然还有Service, Broadcast, ContentProvider).我们会发现,打开一个App,然后不同的Activity展示着不同的内容,可以不断地打开不同的Activity,同时也可以通过Back按键,返回到之前打开过的Activity,并且还是保持原来Activity的模样.通过Home键,切换到主界面,又可以启动别的App,把原本的App放在后台,等待下次唤醒.这整个流程,和几个名词有关,只要弄清这几个名词,便可以搞懂Activity的启动.

  1. 返回栈(Back Stack): 通过名字,我们可以知道,返回栈是用于实现我们通过点击Back按键,返回上一个Activity使用的,以栈为结构,实现后进先出(first in,last out)的模型.返回栈只有一个(提供给用户使用的).但是栈中的任务(Task)可能不只一个,例如App可以启动系统浏览器App,那么App是一个Task,浏览器App也是一个Task.

  2. 任务(Task): Android把n个Activity组成一个任务(Task),通常来说,同一个Activity的启动都属于一个Task(也有特殊情况,后文说明),当Task中的Activity通过Back按键返回完之后,也就是Task中一个Activity都没有的情况下,Task就不存在了.后台会有多个Task存在,因为我们可以通过Home键返回到Launch界面,去启动新的App.也可以把处于后台的Task,移到前台来.移到前台的Task会放入返回栈.

  3. 进程(Process): 在Android中,一般来说一个App就是一个进程,当然你也可以通过设置,使得某些Activity处于不同的进程.进程与Task没有必然联系.同一个Task的Activity不一定是同一个进程.

  4. 应用(Application): Application其实就是一些Activity, Service…组合而成的.以.apk为后缀存储于文件系统当中.系统也自带Application,例如Email, Calendar, Browser…,需要注意的是,不同Application中的Activity也可以在同一个Task当中,如App1启动了App2中的Activity.

Note: 返回栈可以放不止一个的Task, Task中的Activity不一定来之同一个Application,同时Activity也不一定运行在同一个进程当中.

接下来,说一下Activity启动的问题.每个Activity启动,都会被压倒Task当中,一般情况,每启动一个Activity都会按顺序压入Task,但实际情况不全都是这样.例如Activity中,A->B->C,这个时候,如果C要打开A,到底是用原本有的A呢还是重新实例化一个新的A呢.所以根据现实情况,一般情况的操作不能完全满足我们的要求.
所以Android在启动Activity的时候提供了两种方式,来影响Activity启动在Task中的不同表现.

方式一:在AndroidManifest文件中,对Activity添加属性(只列举与Task有关的属性),

  1. taskAffinity
  2. launchMode
    • standard
    • singleTop
    • singleTask
    • singleInstance
  3. allowTaskReparening
  4. clearTaskOnLaunch
  5. alwaysRetainTaskState
  6. finishOnTaskLaunch

方式二:通过Intent启动Activity,对Intent设置Flag,

  1. FLAG_ACTIVITY_NEW_TASK
  2. FLAG_ACTIVITY_CLEAR_TOP
  3. FLAG_ACTIVITY_SINGLE_TOP
因此,如果 Activity A 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B 应该如何与当前任务关联。如果这两个 Activity 均定义 Activity B 应该如何与任务关联,则 Activity A 的请求(如 Intent 中所定义)优先级要高于 Activity B 的请求(如其清单文件中所定义

首先说一下launchMode:

“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。但是,如果收到针对 A 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 "singleTop" 也是如此。

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

“singleTask” 系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。
上面说的singleTask实例化会位于新任务的底部,有一个条件,就是taskAffinity.需要亲和性与原本的不同
1. 假如A->B->C->D,B为一个singleTask,那么就会调用B的onNewIntent(),同时C,D都会被移除

注:尽管 Activity 在新任务中启动,但是用户按“返回”按钮仍会返回到前一个 Activity。因为这个时候两个Task都存在于返回栈中.

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

使用 launchMode 属性为 Activity 指定的行为可由 Intent 附带的 Activity 启动标志替代

使用Intent标志:

FLAG_ACTIVITY_NEW_TASK 在新任务中启动 Activity。如果已为正在启动的 Activity 运行任务,则该任务会转到前台并恢复其最后状态,同时 Activity 会在 onNewIntent() 中收到新 Intent。

正如前文所述,这会产生与 "singleTask" launchMode 值相同的行为。

FLAG_ACTIVITY_SINGLE_TOP 如果正在启动的 Activity 是当前 Activity(位于返回栈的顶部),则 现有实例会接收对 onNewIntent() 的调用,而不是创建 Activity 的新实例。

正如前文所述,这会产生与 "singleTop" launchMode 值相同的行为。

FLAG_ACTIVITY_CLEAR_TOP 如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 onNewIntent() 将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例。

产生这种行为的 launchMode 属性没有值。

FLAG_ACTIVITY_CLEAR_TOP 通常与 FLAG_ACTIVITY_NEW_TASK 结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。

注:如果指定 Activity 的启动模式为 "standard",则该 Activity 也会从堆栈中删除,并在其位置启动一个新实例,以便处理传入的 Intent。 这是因为当启动模式为 "standard" 时,将始终为新 Intent 创建新实例。

taskAffinity

taskAffinity用来控制Activity优先属于哪个任务.默认情况下一个Application中的所有Activity属于同一个Task.taskAffinity的属性取值为字符串值,默认情况下的值为软件的包名.因此要不同于包名才能区分.

在两种情况下,taskAffinity会起作用:

  • 启动 Activity 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 标志。

    默认情况下,新 Activity 会启动到调用 startActivity() 的 Activity 任务中。它将推入与调用方相同的返回栈。 但是,如果传递给 startActivity() 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 标志,则系统会寻找其他任务来储存新 Activity。这通常是新任务,但未做强制要求。 如果现有任务与新 Activity 具有相同关联,则会将 Activity 启动到该任务中。 否则,将开始新任务。

    如果此标志导致 Activity 开始新任务,且用户按“主页”按钮离开,则必须为用户提供导航回任务的方式。 有些实体(如通知管理器)始终在外部任务中启动 Activity,而从不作为其自身的一部分启动 Activity,因此它们始终将 FLAG_ACTIVITY_NEW_TASK 放入传递给 startActivity() 的 Intent 中。请注意,如果 Activity 能够由可以使用此标志的外部实体调用,则用户可以通过独立方式返回到启动的任务,例如,使用启动器图标(任务的根 Activity 具有 CATEGORY_LAUNCHER Intent 过滤器;请参阅下面的启动任务部分)。

  • Activity 将其 allowTaskReparenting 属性设置为 “true”。

    在这种情况下,Activity 可以从其启动的任务移动到与其具有关联的任务(如果该任务出现在前台)。

    例如,假设将报告所选城市天气状况的 Activity 定义为旅行应用的一部分。 它与同一应用中的其他 Activity 具有相同的关联(默认应用关联),并允许利用此属性重定父级。当您的一个 Activity 启动天气预报 Activity 时,它最初所属的任务与您的 Activity 相同。 但是,当旅行应用的任务出现在前台时,系统会将天气预报 Activity 重新分配给该任务并显示在其中。

清理返回栈

如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根任务除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。

您可以使用下列几个 Activity 属性修改此行为:

alwaysRetainTaskState 如果在任务的根 Activity 中将此属性设置为 “true”,则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。

clearTaskOnLaunch 如果在任务的根 Activity 中将此属性设置为 “true”,则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与 alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。 ` finishOnTaskLaunch 此属性类似于 clearTaskOnLaunch,但它对单个 Activity 起作用,而非整个任务。 此外,它还有可能会导致任何 Activity 停止,包括根 Activity。 设置为 “true” 时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。

参考1
参考2