Android Context 的继承体系与底层原理
当你在 Activity 中调用 getString()、startActivity()、getSystemService() 时,你可能从未想过一个问题:这些方法到底是谁在执行? 答案不是 Activity 本身,而是隐藏在它身后的一个叫 ContextImpl 的幕后工作者。
Context 是 Android 中最基础、最核心、也最容易被误用的概念。它不是一个简单的"上下文对象",而是一套精心设计的委托体系——一个融合了装饰者模式、控制反转和职责隔离的架构杰作。
本文将从源码层面彻底拆解 Context 的继承体系,讲清楚 Activity、Service、Application 三种 Context 的本质区别,以及为什么用错 Context 会导致内存泄漏甚至崩溃。
设计哲学:为什么需要 Context
在 Android 的沙箱世界里,每个应用都运行在独立的进程和独立的 Dalvik/ART 虚拟机中。应用不能直接访问系统资源、不能直接调用系统服务、不能直接操作其他应用的数据。
这就像一个被关在隔音房间里的人——你需要一部电话才能和外界沟通。Context 就是这部电话。
它为应用提供了与 Android 系统交互的全部能力:
- 获取资源:
getResources()、getString()、getDrawable() - 启动组件:
startActivity()、startService()、sendBroadcast() - 访问系统服务:
getSystemService()获取 WiFi、蓝牙、传感器等 - 文件与数据库:
openFileOutput()、getSharedPreferences()、openOrCreateDatabase() - 包信息:
getPackageName()、getPackageManager()
没有 Context,你的代码就是一个孤立的 Java 对象,什么系统能力都没有。
继承体系全景:装饰者模式的经典实践
Android 的 Context 体系采用了经典的装饰者模式(Decorator Pattern)。理解这个模式是理解整个 Context 架构的钥匙。
类继承关系图
Context (抽象类)
定义了所有 Context 能力的接口契约
┌──────────┴──────────┐
│ │
ContextWrapper ContextImpl
(装饰者基类) (真正的实现者)
持有 mBase 引用, 所有方法的具体逻辑
所有方法委托给 mBase 都在这里执行
│
┌──────────┼──────────┐
│ │ │
Application Service ContextThemeWrapper
(全局上下文) (服务上下文) (带主题的包装器)
│
Activity
(界面上下文)
为什么要这么设计
你可能会问:为什么不让 Activity 直接继承 ContextImpl?
答案是职责隔离。设想一下如果 Activity 直接继承 ContextImpl 会发生什么:
- ContextImpl 的内部实现细节全部暴露给了 Activity 子类
- Activity 想修改某个行为(比如资源加载策略)就必须覆写 ContextImpl 的方法,破坏封装
- 如果 Google 要修改 ContextImpl 的内部实现,所有继承它的类都可能受影响
装饰者模式的精妙之处在于:把"能力的定义"和"能力的实现"彻底分离。
Context:定义接口——"我能做什么"ContextImpl:提供实现——"具体怎么做"ContextWrapper:提供代理——"我不做,让别人做"Activity / Service / Application:扩展行为——"我在代理的基础上加点自己的东西"
这就像一家公司的组织架构:CEO(Activity)不需要知道财务报表怎么算,他只需要把财务工作委托给 CFO(ContextImpl)。如果哪天换了 CFO,CEO 的工作方式完全不受影响。
核心角色逐个拆解
ContextImpl:真正干活的人
ContextImpl 是 Context 抽象类的唯一具体实现。你在应用中调用的每一个 Context 方法,最终都会落到 ContextImpl 的对应方法上执行。
// frameworks/base/core/java/android/app/ContextImpl.java(简化)
class ContextImpl extends Context {
// 持有应用的包信息,用于加载资源、获取 ClassLoader
final LoadedApk mPackageInfo;
// 资源管理器,负责加载 XML 布局、图片、字符串等
private Resources mResources;
// ContentResolver,用于访问 ContentProvider
private final ContentResolver mContentResolver;
// 外部持有者引用(Activity / Service / Application)
private Context mOuterContext;
@Override
public Object getSystemService(String name) {
// 从全局的 SystemServiceRegistry 中查找对应的系统服务
return SystemServiceRegistry.getSystemService(this, name);
}
@Override
public void startActivity(Intent intent) {
// 注意:ContextImpl 启动 Activity 必须加 NEW_TASK 标志
// 因为它没有 Activity 任务栈的概念
mMainThread.getInstrumentation().execStartActivity(...);
}
@Override
public Resources getResources() {
return mResources;
}
}
关键发现:ContextImpl 持有 mOuterContext 引用,指向包裹它的 Activity/Service/Application。这形成了一个双向引用:Activity 通过 mBase 指向 ContextImpl,ContextImpl 通过 mOuterContext 指回 Activity。这个设计让 ContextImpl 在需要时可以获取外部组件的信息。
ContextWrapper:装饰者的骨架
ContextWrapper 是整个装饰者模式的中枢环节。它的代码极其简单,却承担着至关重要的代理职责:
// frameworks/base/core/java/android/content/ContextWrapper.java
public class ContextWrapper extends Context {
// 被代理的真正 Context 实现(即 ContextImpl)
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
// 注入真正的 Context 实现
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
// 以下所有方法都是纯粹的委托转发
@Override
public Resources getResources() {
return mBase.getResources();
}
@Override
public Object getSystemService(String name) {
return mBase.getSystemService(name);
}
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
// ... 几十个方法,全部是 return mBase.xxx()
}
注意 attachBaseContext 的防重入检查:一旦 mBase 被设置,就不能再改。这保证了 Context 绑定关系的不可变性——一个 Activity 的底层 Context 在其整个生命周期中不会被替换。
ContextThemeWrapper:为 UI 注入主题
Activity 和 Service 最大的区别是什么?Activity 有界面,Service 没有。界面意味着主题(Theme)——字体大小、颜色、按钮样式等。
ContextThemeWrapper 就是为此而生的中间层:
// frameworks/base/core/java/android/view/ContextThemeWrapper.java
public class ContextThemeWrapper extends ContextWrapper {
// 主题资源 ID(如 R.style.AppTheme)
private int mThemeResource;
// 解析后的 Theme 对象
private Resources.Theme mTheme;
@Override
public void setTheme(int resid) {
mThemeResource = resid;
// 延迟初始化:不立刻解析,等到第一次 getTheme() 时再解析
mTheme = null;
}
@Override
public Resources.Theme getTheme() {
if (mTheme == null) {
// 从资源系统中加载并应用主题
mTheme = getResources().newTheme();
mTheme.applyStyle(mThemeResource, true);
}
return mTheme;
}
}
为什么 Service 不继承 ContextThemeWrapper? 因为 Service 不需要绘制 UI,强行给它一个主题系统只会浪费内存。这就是继承体系分叉的根本原因——按需组合能力。
三种 Context 的创建流程:源码级剖析
每种 Context 都是由 ActivityThread(应用进程的主线程管理者)在特定时机创建的。理解创建流程,才能真正明白它们的区别。
Application Context 的诞生
Application 是整个应用中最早被创建的 Context,在任何 Activity 或 Service 之前:
// frameworks/base/core/java/android/app/LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass, ...) {
// 1. 创建 ContextImpl —— Application 的真正引擎
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// 2. 通过反射创建 Application 实例(你在 Manifest 中声明的那个类)
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
// 3. 双向绑定:ContextImpl 记住自己的外壳是谁
appContext.setOuterContext(app);
return app;
}
在 Instrumentation.newApplication() 内部:
// frameworks/base/core/java/android/app/Instrumentation.java
static public Application newApplication(Class<?> clazz, Context context) {
Application app = (Application) clazz.newInstance();
// 这一步调用 ContextWrapper.attachBaseContext()
// 将 ContextImpl 注入 Application 的 mBase
app.attach(context);
return app;
}
关键点:createAppContext() 创建的 ContextImpl 不包含任何 UI 相关信息(没有 Display、没有 Theme)。这就是为什么 Application Context 不能用来显示 Dialog 的根本原因。
Activity Context 的诞生
Activity 的 Context 创建发生在 performLaunchActivity() 中,如果你读过本系列的 Activity 生命周期文章,会对这个方法非常熟悉:
// frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, ...) {
// 1. 创建 Activity 专属的 ContextImpl
// 注意:这里调用的是 createActivityContext,不是 createAppContext
ContextImpl appContext = createBaseContextForActivity(r);
// 2. 反射创建 Activity 实例(此时它还是一个"空壳")
Activity activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// 3. 史诗级方法:attach() —— 为空壳注入灵魂
activity.attach(appContext, this, getInstrumentation(),
r.token, r.ident, app, r.intent, r.activityInfo, ...);
return activity;
}
createBaseContextForActivity 与 createAppContext 的核心区别:
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
// 创建 Activity 级别的 ContextImpl,携带了显示信息和配置
ContextImpl appContext = ContextImpl.createActivityContext(
this,
r.packageInfo,
r.activityInfo, // ← Activity 的元信息
r.token, // ← IBinder Token,窗口身份证
displayId, // ← 显示屏 ID
r.overrideConfig // ← 配置覆盖(如深色模式)
);
return appContext;
}
Activity 的 ContextImpl 比 Application 的多了三个关键信息:
| 信息 | 作用 |
|---|---|
activityInfo |
包含 Manifest 中声明的主题、屏幕方向等元数据 |
token (IBinder) |
窗口的身份令牌,Dialog 和 Toast 需要它来确认父窗口 |
displayId + overrideConfig |
显示屏信息和配置覆盖,支持多屏和深色模式 |
这就是 Activity Context 能显示 Dialog 而 Application Context 不能的底层真相:Dialog 需要 token 来确定它挂在哪个窗口上,而 Application Context 根本没有这个 token。
Service Context 的诞生
Service 的创建流程与 Application 类似,使用的是 createAppContext 而非 createActivityContext:
// frameworks/base/core/java/android/app/ActivityThread.java
private void handleCreateService(CreateServiceData data) {
// 1. 创建 ContextImpl —— 与 Application 同级别,无 UI 信息
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
// 2. 反射创建 Service 实例
Service service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
// 3. 双向绑定
context.setOuterContext(service);
service.attach(context, this, data.info.name, data.token, app, ...);
// 4. 触发生命周期
service.onCreate();
}
核心发现:Service 的 ContextImpl 和 Application 的 ContextImpl 是同级别的——都没有 UI 信息。但它们是不同的实例,生命周期也不同。
一个应用到底有几个 Context
这是一个被频繁讨论但经常答错的问题。精确的公式是:
Context 实例总数 = Activity 数量 + Service 数量 + 1(Application)
BroadcastReceiver 和 ContentProvider 不算,因为它们不是 Context 的子类:
BroadcastReceiver:onReceive(Context context, ...)中的 context 是系统传入的一个受限 Context(ReceiverRestrictedContext),不是它自己ContentProvider:通过getContext()获取的是 Application Context,它自己也不是 Context
用一个具体例子说明:
一个应用有 5 个 Activity、2 个 Service、1 个 Application
Context 总数 = 5 + 2 + 1 = 8
每个 Context 背后都有一个独立的 ContextImpl 实例为其服务。
三种 Context 的能力对比
不同类型的 Context 能做的事情不一样。这不是人为限制,而是它们拥有的信息不同所导致的自然结果:
| 操作 | Application | Service | Activity | 原因 |
|---|---|---|---|---|
| 显示 Dialog | ❌ | ❌ | ✅ | 需要窗口 Token |
| 启动 Activity | ⚠️ | ⚠️ | ✅ | 非 Activity 启动需加 FLAG_ACTIVITY_NEW_TASK |
| Layout Inflate | ⚠️ | ⚠️ | ✅ | 不带 Theme 会丢失样式 |
| 启动 Service | ✅ | ✅ | ✅ | 不依赖 UI |
| 发送广播 | ✅ | ✅ | ✅ | 不依赖 UI |
| 注册广播 | ✅ | ✅ | ✅ | 不依赖 UI |
| 加载资源 | ✅ | ✅ | ✅ | 不依赖 UI |
| 获取系统服务 | ✅ | ✅ | ✅ | 不依赖 UI |
⚠️ 表示"能用但有坑":
- 用 Application Context 启动 Activity 不会崩溃,但必须加
FLAG_ACTIVITY_NEW_TASK,因为系统不知道该把新 Activity 放进哪个任务栈 - 用 Application Context 加载布局不会崩溃,但所有主题样式会丢失,因为 Application 没有 Theme
为什么 Dialog 必须用 Activity Context
这是最经典的坑。让我们从源码看为什么:
// frameworks/base/core/java/android/app/Dialog.java
Dialog(Context context, int themeResId, boolean createContextThemeWrapper) {
// Dialog 从传入的 Context 中获取 WindowManager
mWindowManager = (WindowManager) context.getSystemService(
Context.WINDOW_SERVICE);
// 创建 Dialog 自己的 Window
mWindow = new PhoneWindow(mContext);
}
当 Dialog 调用 show() 时:
public void show() {
// 向 WindowManagerService 添加窗口
// 这一步需要一个有效的 Token 来标识父窗口
mWindowManager.addView(mDecor, layoutParams);
}
WindowManagerService 收到添加窗口的请求后,会检查 layoutParams.token:
- 如果 token 来自一个合法的 Activity 窗口 → 允许添加
- 如果 token 为 null(Application Context 没有 token)→ 抛出
BadTokenException
这就是为什么用 Application Context 显示 Dialog 会崩溃——不是代码层面的限制,而是窗口系统层面的安全校验。
内存泄漏:Context 使用的最大陷阱
Context 使用不当导致的内存泄漏,是 Android 开发中最常见也最隐蔽的 BUG 之一。
泄漏的本质
内存泄漏的根因只有一个:长生命周期的对象持有了短生命周期对象的强引用。
长生命周期对象 短生命周期对象
┌─────────────┐ ┌─────────────┐
│ 单例 / 静态 │──引用──→│ Activity │
│ 变量 / 线程 │ │ Context │
└─────────────┘ └─────────────┘
│ │
│ 永远不会被 GC │ 用户按返回键后应该被 GC
│ │ 但因为被强引用,无法回收
│ │
└── GC Root ────────────┘ ← 泄漏!
一个 Activity 对象通常持有整个视图树、Bitmap、Drawable 等大量资源。如果一个 Activity 因为被静态引用而无法回收,泄漏的内存可能达到几十甚至上百 MB。
经典泄漏场景与修复
场景一:单例持有 Activity Context
// ❌ 危险代码:单例的生命周期 = 进程生命周期
object NetworkManager {
private lateinit var context: Context
fun init(context: Context) {
// 如果传入的是 Activity,Activity 销毁后也不会被 GC
this.context = context
}
}
// ✅ 安全代码:使用 Application Context
object NetworkManager {
private lateinit var context: Context
fun init(context: Context) {
// applicationContext 的生命周期 = 进程,不存在泄漏问题
this.context = context.applicationContext
}
}
场景二:匿名内部类隐式持有外部类引用
// ❌ 危险代码
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 匿名 Runnable 隐式持有 MyActivity.this
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
// 10 秒后执行,但如果 Activity 在这 10 秒内销毁了
// Runnable 仍然持有 Activity 引用 → 泄漏
updateUI()
}, 10_000)
}
}
// ✅ 安全代码:在 onDestroy 中移除回调
class MyActivity : AppCompatActivity() {
private val handler = Handler(Looper.getMainLooper())
private val updateRunnable = Runnable { updateUI() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handler.postDelayed(updateRunnable, 10_000)
}
override fun onDestroy() {
super.onDestroy()
// 及时移除,断开引用链
handler.removeCallbacks(updateRunnable)
}
}
Context 选择决策树
当你不确定该用哪个 Context 时,按照这个流程判断:
需要使用 Context
│
▼
这个操作和 UI 有关吗?
(显示 Dialog、Inflate 布局、加载带主题的资源)
│
├── 是 → 必须使用 Activity Context
│
└── 否 → 这个 Context 会被长生命周期对象持有吗?
│
├── 是 → 必须使用 Application Context
│ (单例、静态变量、后台线程)
│
└── 否 → 两者皆可,优先 Application Context
更安全
getApplicationContext 与 getBaseContext 的区别
这两个方法经常被混淆,但它们指向的是完全不同的对象:
Activity
│
├── getBaseContext()
│ └── 返回 mBase(即 ContextImpl 实例)
│ 这是 Activity 自己的 Context 引擎
│ 生命周期与 Activity 相同
│
└── getApplicationContext()
└── 返回 Application 对象
这是全局唯一的 Context
生命周期与进程相同
// ContextWrapper.java
public Context getBaseContext() {
return mBase; // 返回注入的 ContextImpl
}
// ContextImpl.java
@Override
public Context getApplicationContext() {
// 通过 LoadedApk 获取全局的 Application 对象
return (mPackageInfo != null)
? mPackageInfo.getApplication()
: mMainThread.getApplication();
}
实际开发中几乎不需要用 getBaseContext()。它主要用于框架开发或自定义 ContextWrapper 的场景。日常开发中,用 this(当前组件的 Context)或 applicationContext 即可。
BroadcastReceiver 的特殊 Context
BroadcastReceiver 不是 Context 的子类,但它的 onReceive() 方法会收到一个系统传入的 Context 参数。这个 Context 有什么特殊之处?
对于静态注册的广播接收器(在 Manifest 中声明的),系统传入的是一个 ReceiverRestrictedContext——这是 ContextImpl 的一个内部子类,专门用来限制某些操作:
// frameworks/base/core/java/android/app/ContextImpl.java
private static class ReceiverRestrictedContext extends ContextWrapper {
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
// 禁止在广播接收器中注册新的广播接收器
throw new ReceiverCallNotAllowedException(...);
}
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
// 禁止在广播接收器中绑定服务
throw new ReceiverCallNotAllowedException(...);
}
}
为什么要限制? 因为 BroadcastReceiver.onReceive() 的执行窗口极短(约 10 秒),在这么短的时间内注册广播或绑定服务是不安全的——如果 onReceive() 结束了但绑定的服务还在回调,就会出现悬挂引用。
而对于动态注册的广播接收器(代码中通过 registerReceiver() 注册的),系统传入的就是注册时使用的那个 Context 本身,没有额外限制。
总结:Context 体系的架构之美
Android Context 的设计体现了几个重要的软件工程原则:
-
装饰者模式:通过 ContextWrapper 的委托机制,将接口定义与实现分离,允许在不修改 ContextImpl 的前提下扩展行为
-
按需组合:Activity 需要主题 → 多继承一层 ContextThemeWrapper;Service 不需要 → 直接继承 ContextWrapper。不同组件只承载自己需要的能力
-
控制反转:组件不能自己创建 Context,必须由系统(ActivityThread)在创建组件时注入。这保证了系统对资源访问的完全控制
-
最小权限原则:BroadcastReceiver 收到的是受限 Context,Application 没有窗口 Token。每种 Context 只拥有它需要的能力,不多不少
理解了 Context 的继承体系和创建流程,你就能回答一个本质性的问题:所有 Context 方法的最终执行者都是 ContextImpl,不同组件只是用不同的方式包装和限制了对它的访问。这就是 Android Context 体系的全部真相。