不止是 `FLAG_ACTIVITY_NEW_TASK`:深入Android Context的“血统”与 `startActivity` 的隐藏规则

197 阅读3分钟

一句话总结

** Activity 的 startActivity 是“有家可归”,自动帮你找任务栈;而来自 ApplicationService 等非 Activity 上下文的 startActivity 是“流浪汉”,必须手动用 FLAG_ACTIVITY_NEW_TASK 指定任务栈,否则系统会因不知所措而让应用崩溃!


一、问题的根源:Context 的两种“血统”

在Android中,Context 并非铁板一块,它至少有两种截然不同的“血统”:

  1. Activity Context

    • 血统特点:与UI界面强相关,它内置了一个任务栈(Task)的引用。它知道自己“身在何处”。
    • 生命周期:与 Activity 绑定,是短暂的。
  2. Activity Context (以 Application Context 为代表)

    • 血统特点:与UI无关,它没有关联的任务栈信息。它是一个全局的、游离的存在。
    • 生命周期:与整个应用的进程绑定,是长寿的。

Service Context 也属于非 Activity Context,因为它同样没有UI和任务栈。


二、一行Flag的背后:深入 startActivity 的系统决策之旅

当我们调用 context.startActivity(intent) 时,请求最终会通过 Binder 交给系统核心服务 ActivityTaskManagerService (ATMS) 处理。ATMS 就像一个调度中心,它启动新 Activity 的核心逻辑是:

  1. 查找任务栈:ATMS 首先要确定将这个新的 Activity 放入哪个任务栈。

  2. 决策依据

    • 如果 startActivity 是由一个 Activity Context 发起的,ATMS 可以轻易地从这个 Context 中获取到它当前所在的任务栈,然后将新的 Activity 压入这个栈顶。一切顺理成章。
    • 如果 startActivity 是由一个 ApplicationService Context 发起的,这个 Context 自身不带任何任务栈信息。ATMS 会感到困惑:“我该把它放到哪里去?”
  3. 异常抛出:在这种困惑下,为了避免不确定的行为,ATMS 的策略就是直接拒绝服务,抛出著名的异常:Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.

FLAG_ACTIVITY_NEW_TASK 的作用,就是给迷茫的 ATMS 一个明确的指令:“不要管调用者是谁了,请为这个新的 Activity 创建一个全新的任务栈(或者复用一个亲和性相同的已有任务栈)。”


三、FLAG_ACTIVITY_NEW_TASK 的真实工作原理

它的工作机制比“创建新栈”更智能,与 AndroidManifest.xml 中的 taskAffinity(任务亲和性)属性紧密相关:

  1. 系统会检查是否存在一个与即将启动的 ActivitytaskAffinity 相匹配的已有任务栈。
  2. 如果存在:系统不会创建新栈,而是将那个匹配的任务栈整体带到前台,然后将新的 Activity 压入其中。
  3. 如果不存在:系统才会创建一个新的任务栈,并将该 Activity 作为新栈的根 Activity (Root Activity)。

四、实践中的最佳选择与反思

1. “就近原则”:选择最合适的 Context

  • 凡是与UI相关的操作(如创建DialogPopupWindow、加载布局LayoutInflater),都应优先使用 Activity Context
  • 凡是与应用全局状态相关、生命周期长的操作(如初始化单例、启动服务),才应使用 Application Context

2. 内存泄漏的“头号元凶”

切勿将 Activity Context 的引用传递给一个长生命周期的对象(如静态单例),这会导致 Activity 销毁后其 Context 依然被持有,无法被垃圾回收器回收,造成严重的内存泄漏。在这种场景下,使用 Application Context 是更安全的选择。

场景推荐使用的 Context原因
启动新 ActivityActivity Context (优先)保持任务栈的连贯性,符合用户预期。
创建 DialogActivity Context (必须)Dialog 必须附着在一个 Activity 窗口上。
初始化单例/全局工具Application Context (必须)避免因 Activity 销毁导致内存泄漏。
Service 中启动 ActivityApplication Context 或 Service Context必须配合 FLAG_ACTIVITY_NEW_TASK