Activity 里的 this 为什么能拿到系统服务
Activity 里的 this 为什么能拿到系统服务
Activity 没有直接实现 getSystemService() 的服务查找逻辑,业务代码却可以调用 this.getSystemService()。真正执行查找的是 Activity 内部绑定的 ContextImpl;Activity 继承自 ContextWrapper,多数 Context 调用都会被转发给这个内部对象。
Context 是接口边界,不是唯一对象
Context 是抽象类,定义资源访问、组件启动、文件目录和系统服务等应用环境能力。 ContextImpl 保存 LoadedApk、ActivityThread、资源与组件相关状态,并实现这些能力。 ContextWrapper 持有一个 mBase,把调用转发给它;Activity、Service 和 Application 都通过 这层包装暴露 Context API。
因此,Activity.this 与 getApplicationContext() 既不是同一个实例,也不代表完全相同的 环境。Activity Context 与特定 Activity、显示、主题和窗口 token 相关;Application Context 生命周期接近进程,适合不需要界面语义的长期对象。
ContextImpl 何时绑定到组件
组件不是先构造完再临时寻找一个全局 Context。ActivityThread 在创建组件的流程中先准备 对应的 ContextImpl,随后通过组件的 attach() 调用 attachBaseContext(),把它保存到 ContextWrapper.mBase。
Activity 的入口可从 frameworks/base/core/java/android/app/ActivityThread.java 的 performLaunchActivity() 跟到 Activity.attach();Service 则由 handleCreateService() 创建 Context 后调用 Service.attach()。Application 的 Context 在应用绑定阶段由 LoadedApk 与 ContextImpl.createAppContext() 协作建立。
组件拥有各自 Context 的原因不只是 API 形式统一。不同 Activity 可能位于不同 Display,使用 不同主题或经过 Configuration 覆盖;如果把 Application Context 当作 Activity Context 使用, 资源和视觉服务就可能缺少正确的显示与窗口信息。
getSystemService() 如何找到对象
调用从 Activity 进入 ContextWrapper 后被转给 ContextImpl.getSystemService(),再进入 SystemServiceRegistry。注册表保存服务名、服务类型与 ServiceFetcher 的映射,由不同 Fetcher 决定对象如何创建和缓存。
所以“每次 getSystemService() 都 new 一个对象”和“所有 Context 永远共享同一个对象”都不 准确。缓存策略取决于具体服务:有的按进程缓存,有的按 Context 缓存,有的需要把当前 Context 传给管理器。分析某项服务时,应查看它在 SystemServiceRegistry 中的注册方式。
Android 14 会对从非视觉 Context 获取部分视觉服务的行为发出诊断信息。这类警告不是说 Application Context 完全不能调用 getSystemService(),而是说明目标服务需要 UI Context 携带的显示或窗口语义。
什么时候该用哪种 Context
与 Activity 窗口、主题、Display 或界面生命周期有关的对象,应使用对应 Activity Context。 Dialog、主题化 LayoutInflater 和窗口管理是典型场景。生命周期跨越 Activity 的仓库、数据库 或进程级单例,通常只需 Application Context,避免静态对象长期持有已经销毁的 Activity。
Service Context 也不是“没有界面的 Activity Context”。它代表 Service 自身的组件环境;若 代码最终需要创建窗口,还必须满足窗口类型、token 和权限要求,换一个 Context 不能绕过 WMS 校验。
排查 Context 误用时,可以先打印对象类型与 base Context,再结合日志定位:
Log.d("Ctx", "ctx=${context.javaClass.name}")
if (context is ContextWrapper) {
Log.d("Ctx", "base=${context.baseContext.javaClass.name}")
}还要检查对象保存位置和生命周期:静态字段是否持有 Activity,异步任务返回时 Activity 是否 已经销毁,创建视觉服务时 Context 是否属于目标 Display。只看到 ContextImpl 类名并不能 证明资源配置正确,因为关键差异还保存在实例状态中。
源码入口
源码阅读可以从以下位置进入:
| 文件 | 关注点 |
|---|---|
frameworks/base/core/java/android/content/Context.java | API 抽象边界 |
frameworks/base/core/java/android/content/ContextWrapper.java | mBase 与调用转发 |
frameworks/base/core/java/android/app/ContextImpl.java | Context 的具体实现与创建入口 |
frameworks/base/core/java/android/app/SystemServiceRegistry.java | 服务注册、创建和缓存策略 |
frameworks/base/core/java/android/app/ActivityThread.java | 组件创建与 Context 绑定 |
Activity 能调用系统服务,是因为组件创建阶段已经把合适的 ContextImpl 绑定给它。选择 Context 时真正要回答的不是“这个对象能不能调用 API”,而是“它携带的生命周期、显示和配置 是否与当前工作匹配”。