一句话总结
** Activity 的 startActivity 是“有家可归”,自动帮你找任务栈;而来自 Application 或 Service 等非 Activity 上下文的 startActivity 是“流浪汉”,必须手动用 FLAG_ACTIVITY_NEW_TASK 指定任务栈,否则系统会因不知所措而让应用崩溃!
一、问题的根源:Context 的两种“血统”
在Android中,Context 并非铁板一块,它至少有两种截然不同的“血统”:
-
ActivityContext:- 血统特点:与UI界面强相关,它内置了一个任务栈(Task)的引用。它知道自己“身在何处”。
- 生命周期:与
Activity绑定,是短暂的。
-
非
ActivityContext (以ApplicationContext 为代表) :- 血统特点:与UI无关,它没有关联的任务栈信息。它是一个全局的、游离的存在。
- 生命周期:与整个应用的进程绑定,是长寿的。
Service Context 也属于非 Activity Context,因为它同样没有UI和任务栈。
二、一行Flag的背后:深入 startActivity 的系统决策之旅
当我们调用 context.startActivity(intent) 时,请求最终会通过 Binder 交给系统核心服务 ActivityTaskManagerService (ATMS) 处理。ATMS 就像一个调度中心,它启动新 Activity 的核心逻辑是:
-
查找任务栈:ATMS 首先要确定将这个新的
Activity放入哪个任务栈。 -
决策依据:
- 如果
startActivity是由一个ActivityContext 发起的,ATMS 可以轻易地从这个Context中获取到它当前所在的任务栈,然后将新的Activity压入这个栈顶。一切顺理成章。 - 如果
startActivity是由一个Application或ServiceContext 发起的,这个Context自身不带任何任务栈信息。ATMS 会感到困惑:“我该把它放到哪里去?”
- 如果
-
异常抛出:在这种困惑下,为了避免不确定的行为,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(任务亲和性)属性紧密相关:
- 系统会检查是否存在一个与即将启动的
Activity的taskAffinity相匹配的已有任务栈。 - 如果存在:系统不会创建新栈,而是将那个匹配的任务栈整体带到前台,然后将新的
Activity压入其中。 - 如果不存在:系统才会创建一个新的任务栈,并将该
Activity作为新栈的根Activity(Root Activity)。
四、实践中的最佳选择与反思
1. “就近原则”:选择最合适的 Context
- 凡是与UI相关的操作(如创建
Dialog、PopupWindow、加载布局LayoutInflater),都应优先使用Activity Context。 - 凡是与应用全局状态相关、生命周期长的操作(如初始化单例、启动服务),才应使用
Application Context。
2. 内存泄漏的“头号元凶”
切勿将 Activity Context 的引用传递给一个长生命周期的对象(如静态单例),这会导致 Activity 销毁后其 Context 依然被持有,无法被垃圾回收器回收,造成严重的内存泄漏。在这种场景下,使用 Application Context 是更安全的选择。
| 场景 | 推荐使用的 Context | 原因 |
|---|---|---|
启动新 Activity | Activity Context (优先) | 保持任务栈的连贯性,符合用户预期。 |
创建 Dialog | Activity Context (必须) | Dialog 必须附着在一个 Activity 窗口上。 |
| 初始化单例/全局工具 | Application Context (必须) | 避免因 Activity 销毁导致内存泄漏。 |
在 Service 中启动 Activity | Application Context 或 Service Context | 必须配合 FLAG_ACTIVITY_NEW_TASK。 |