Android Context 相关问题
面试一句话:Context 是“当前应用上下文”的抽象接口,真正干活的是 ContextImpl,ContextWrapper 代理一层给 Activity/Service/Application 用;Application 的 Context 进程单例、生命周期最长,Activity/Service 各有一个。Context 个数 = 1 个 Application + N 个 Activity + M 个 Service。详见笔记《Android 面试·分章系统复习》相关章节。
阅读建议:先看 面试两问 和 选型,再 是什么 / 整体结构 / 创建时机,最后 注意点。
面试两问(结论前置)
1. Application、Activity、Service 的 Context 有什么区别?
| 类型 | 生命周期 | 典型用途 | 注意 |
|---|---|---|---|
| Application | 进程级单例,与进程同生共死 | 全局配置、单例、getSharedPreferences、getSystemService | 不要长期持 Activity 引用,易泄漏 |
| Activity | 随 Activity 创建/销毁 | 启动 Activity、布局 inflate、主题、startActivityForResult | 带主题,和界面绑定 |
| Service | 随 Service 创建/销毁 | 后台逻辑、getSystemService、不带 UI 的能力 | 无主题、无 UI |
2. App 里有几个 Context?
1 个 Application + 所有 Activity 实例数 + 所有 Service 实例数(每个 Activity/Service 对应一个 Context)。
选型:什么时候用谁?
- 要存全局单例、跨 Activity 用 → 用 Application 的 Context(getApplicationContext())。
- 要弹 Dialog、inflate 布局、启动 Activity、用主题 → 用 Activity 的 Context(this 或 activity 引用)。
- 在 Service 里拿系统服务、发广播、操作文件 → 用 Service 的 Context(this)。
- 避免:用 Application Context 去 inflate 带主题的布局、弹依赖 Activity 的 Dialog,会丢主题或崩溃;在静态/单例里长期持 Activity 的 Context,会泄漏。
Context 是什么
- 功能:表示“当前应用”的上下文,是访问应用资源、系统服务、启动组件的统一入口。
- 启动组件:startActivity、startService、sendBroadcast。
- 资源与类:getResources()、getAssets()、getClassLoader()。
- 系统服务:getSystemService()(LayoutInflater、ActivityManager 等)。
- 实现:Context 是抽象接口;ContextImpl 是真正实现类(@hide);ContextWrapper 持有一个 ContextImpl(mBase),把所有调用委托给 mBase,Activity/Service/Application 继承 Wrapper 而不直接继承 Impl。
Context 的整体结构
| 类型 | 说明 |
|---|---|
| Context | 抽象接口,定义 getResources、startActivity、getSystemService 等能力。 |
| ContextWrapper | 代理类,内部 mBase 指向 ContextImpl,所有调用委托给 mBase;子类可改写行为而不改 Impl。 |
| ContextImpl | 系统内部实现类(@hide),真正干活的;一个进程里会有多份(Application 一份,每个 Activity/Service 各一份)。 |
ContextWrapper 官方注释:Proxying implementation of Context that simply delegates all of its calls to another Context. Can be subclassed to modify behavior without changing the original Context.
继承与组合关系(Activity、Service、Application 都通过 ContextWrapper 持有 ContextImpl):
案例:Activity 的“带主题”Context
- 需求:Activity 需要带主题的 Context——
getTheme()、getResources()要跟当前 Activity 的 theme 一致,这样 inflate 布局、弹 Dialog 才能用对主题。 - 若没有 Wrapper(Activity 直接继承 ContextImpl):Activity 同样只需重写
getTheme()、getResources()等少数方法,从“重写数量”上看和继承 ContextWrapper 没差——所以 Wrapper 的目的不是减少重写,而是换一种结构。 - 真正要解决的两件事:
- ContextImpl 不进入继承链:若 Activity 继承 ContextImpl,则 Impl 会作为公开类型出现在“Activity 的父类”里,Impl 难以 @hide、也难以单独演进。用 Wrapper 后,Impl 只被持有(组合),不被人继承;对外类型是 Context / ContextWrapper / Activity,Impl 是内部实现细节,可随意改动。
- 通用实现与扩展分离:ContextImpl 由框架统一创建、注入(例如 ActivityThread 创建好再塞给 Activity);Activity/Service/Application 只在自己这一层做“扩展”(重写 getTheme 等)。这样“通用能力”集中在 Impl 一份,“谁要改行为”只在 Wrapper 子类里改,问题没有消失,但被限定在 Wrapper 子类这一层,Impl 保持单一职责、稳定复用。
这样设计的优劣
| 维度 | 说明 |
|---|---|
| 优点 | 职责清晰:ContextImpl 只做通用上下文能力,Activity/Service/Application 各在 Wrapper 子类里扩展自己的行为(主题、生命周期等)。易扩展:新增“改写行为”只需在对应子类重写,不动 ContextImpl。Impl 可复用:同一套 ContextImpl 被 Application、Activity、Service 共用,不重复实现。符合组合优于继承:通过持有 mBase 委托,而非继承一个巨大的 Impl。 |
| 劣势 | 多一层调用:每次 Context 调用都要经 Wrapper 再转到 mBase,多一层委托(实际开销很小)。理解成本:新人容易搞不清 Context / ContextWrapper / ContextImpl 三者的关系,需要先建立“接口 → 包装 → 实现”的模型。 |
Context 的创建时机
- Application:进程启动时由系统(如 ActivityThread)创建,进程内单例。
- Activity:ActivityThread 执行 performLaunchActivity 时创建,与 Activity 实例一一对应。
- Service:ActivityThread 执行 handleCreateService 时创建,与 Service 实例一一对应。
常见注意点
- 内存泄漏:静态单例、静态 Handler 等若持有 Activity 的 Context,Activity 销毁后无法回收。能拿 Application Context 就用 getApplicationContext();必须用 Activity 时用弱引用或及时置空。
- 类型用错:用 Application Context 去 startActivity 启动 Activity 没问题,但用 Application Context 去 inflate 布局、弹 Dialog(依赖 Activity 的 Window),可能丢主题或报错,应使用对应 Activity 的 Context。
- 数量:不要误以为“一个 App 只有一个 Context”;Application 一个,每个 Activity/Service 各一个。
参考:语雀原文 Android Context 相关问题