在 Android 开发中,Context(上下文) 是一个贯穿应用生命周期的核心概念,几乎所有的系统服务(如启动 Activity、访问资源、绑定 Service)都依赖它。然而,错误使用 Context 会导致内存泄漏、崩溃等问题。本文从原理、类型到实际使用场景,系统化解析 Context 的设计与最佳实践。
一句话总结 :"UI 操作用 Activity Context,全局服务用 Application Context,避免持有,及时释放。"
一、Context 的核心原理
1、什么是 Context?
-
定义 :Context 是 Android 应用环境的抽象接口,提供了访问应用全局信息的入口,同时也是与系统服务(如
LayoutInflater
、PackageManager
)交互的桥梁。 -
作用:
- 启动组件(Activity、Service、Broadcast)。
- 访问资源(字符串、图片、布局等)。
- 获取系统服务(如
getSystemService()
)。 - 管理应用文件路径和数据库。
2、Context 的类层次结构
-
核心类关系:
java// 源码简析 public abstract class Context { // 核心方法:启动 Activity、访问资源、获取服务等 } // ContextImpl:真正的实现类,由系统创建 class ContextImpl extends Context { ... } // ContextWrapper:包装类,代理模式(如 Activity、Service) public class ContextWrapper extends Context { protected Context mBase; // 实际指向 ContextImpl } // Application、Activity、Service 均继承自 ContextWrapper public class Activity extends ContextWrapper { ... }
-
关键设计 :Context 使用 代理模式 ,具体功能由
ContextImpl
实现,而Activity
、Service
等组件通过ContextWrapper
持有ContextImpl
的引用。
二、Context 的四大类型及使用场景
1、Application Context
- 获取方式 :
getApplicationContext()
。 - 生命周期:与应用进程的生命周期一致(从应用启动到终止)。
- 适用场景 :
- 全局单例对象(如数据库、网络库)。
- 需要长生命周期且不依赖 UI 的操作。
- 注意事项 :
- 不要用于 UI 操作 (如弹 Toast、显示 Dialog),否则可能导致
WindowManager$BadTokenException
。
- 不要用于 UI 操作 (如弹 Toast、显示 Dialog),否则可能导致
2、Activity Context
- 获取方式 :
Activity.this
或View.getContext()
(在 Activity 的 View 中)。 - 生命周期 :与 Activity 绑定(从
onCreate()
到onDestroy()
)。 - 适用场景 :
- 启动其他 Activity。
- 显示 UI 组件(如 Dialog、PopupWindow)。
- 访问与 Activity 关联的主题和资源。
- 注意事项 :
- 避免内存泄漏:禁止在静态变量或后台线程中持有 Activity Context。
3、Service Context
- 获取方式 :
Service.this
。 - 生命周期 :与 Service 绑定(从
onCreate()
到onDestroy()
)。 - 适用场景 :
- 在 Service 中启动组件或绑定服务。
- 执行后台任务时访问系统服务。
- 注意事项 :
- 不要用于 UI 操作(与 Application Context 限制相同)。
4、UI Context(如 Fragment、View)
- 获取方式 :
Fragment.getContext()
或View.getContext()
。 - 本质:实际指向关联的 Activity Context。
- 注意事项 :
- Fragment 在
onAttach()
之前getContext()
返回null
,需判空处理。
- Fragment 在
三、Context 的正确使用指南
1、避免内存泄漏
-
错误示例:单例中直接持有 Activity Context。
kotlinclass AppManager { companion object { // 错误!导致 Activity 无法回收 var context: Context? = null } }
-
解决方案:
- 使用
Application Context
。 - 使用弱引用(
WeakReference
)。
kotlinclass AppManager { companion object { private var weakContext: WeakReference<Context>? = null fun setContext(context: Context) { weakContext = WeakReference(context.applicationContext) } } }
- 使用
2、合理选择 Context 类型
场景 | 推荐 Context 类型 | 原因 |
---|---|---|
启动 Activity | Activity Context | startActivity() 需要任务栈信息(FLAG_ACTIVITY_NEW_TASK 时可用 Application Context) |
显示 Dialog | Activity Context | 必须与当前窗口关联 |
访问主题和资源 | 当前组件 Context(如 Activity) | 确保加载正确的主题资源 |
全局工具类(如网络请求) | Application Context | 生命周期长,避免内存泄漏 |
3、不要滥用 Application Context
- 错误场景 :在 Application Context 中调用
getSystemService()
获取与 UI 相关的服务(如LayoutInflater
),可能导致主题不一致。 - 正确做法:UI 相关操作必须使用 Activity Context。
四、常见问题与解决方案
1、getApplicationContext()
与 getApplication()
的区别
-
getApplicationContext()
:返回 Application 的 Context 对象。 -
getApplication()
:返回 Application 的实例(需在 Activity/Service 中调用)。 -
代码示例 :
kotlin// 获取 Application 实例 val app = getApplication() as MyApplication // 获取 Application Context val appContext = getApplicationContext()
2、如何正确传递 Context?
-
在 Adapter 中 :优先传递 Activity Context 或 Fragment Context。
kotlinclass MyAdapter(private val context: Context) : RecyclerView.Adapter<...>() // 在 Activity 中初始化 val adapter = MyAdapter(this@MainActivity)
3、如何避免 Configuration
变更导致的 Context 失效?
- 问题:屏幕旋转时 Activity 会销毁重建,旧的 Context 可能失效。
- 解决方案 :使用
ViewModel
或onSaveInstanceState()
保存数据,而非直接持有 Context。
五、总结与最佳实践
1、核心原则
- 生命周期对齐:确保 Context 的生命周期不超过其来源组件。
- 最小作用域:优先使用局部 Context,避免全局静态引用。
2、工具推荐
- LeakCanary:检测 Context 泄漏。
- Android Studio Profiler:分析内存中 Context 的持有情况。
3、一句话总结
"UI 操作用 Activity Context,全局服务用 Application Context,避免持有,及时释放。"
通过深入理解 Context 的原理和使用规范,可显著提升应用的稳定性和性能。正确使用 Context 是 Android 开发者的必备技能,也是避免内存泄漏的关键。