Android Context 的 “上下文”

78 阅读10分钟

一、 Context 体系结构与类关系

1.1 Context 抽象类的角色与核心能力

Context = 当前应用运行环境的“句柄” ,它把“我是谁、在哪个包、用什么资源与配置、能访问哪些系统能力”这类信息封装起来,并提供统一 API。

它是一个 抽象类,不直接被实例化,主要能力可以分几类:

  1. 资源与配置访问

    1. getResources():访问 res/ 里的所有资源
    2. getAssets():访问打包进 APK 的资源文件
    3. getPackageName()getApplicationInfo()getPackageManager()
  2. 组件管理 / 应用生命周期相关

    1. startActivity(), startService(), bindService(), sendBroadcast()
    2. registerReceiver(), unregisterReceiver()
  3. 系统服务****获取

  • getSystemService(String name)
LayoutInflater inflater = (LayoutInflater)
        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
PowerManager pm = (PowerManager)
        context.getSystemService(Context.POWER_SERVICE);
  1. 存储与数据

    1. openFileInput(), openFileOutput()(内部文件)
    2. getSharedPreferences()
    3. getDatabasePath(), openOrCreateDatabase()
  2. UI / 主题****相关(由支持主题的子类实现)

    1. setTheme(int resId)
    2. getTheme()
    3. getResources().getDrawable() 等主题敏感资源

设计要点: 对开发者来说,不关心到底是哪个具体子类ActivityApplication 等),只要拿到一个 Context 引用,就能在“一致的 API”下工作。

1.2 核心类的继承与关系示意图

简化版类图(忽略一些细枝末节):

          (抽象)
        +-----------+
        |  Context  |
        +-----------+
             ^
             |
      +-------------+
      | ContextImpl |  ← 真正的实现类(framework 内部)
      +-------------+

             ^
             | (组合/委托:mBase)
      +------------------+
      |  ContextWrapper  |
      +------------------+
             ^
             |
   +----------------------+
   |  ContextThemeWrapper |  ← 支持 Theme 的包装
   +----------------------+
      ^              ^
      |              |
+------------+   +--------+
|  Activity  |   |  ...   |
+------------+

+--------------+
| Application  |  ← 继承 ContextWrapper(不带 UI Theme)
+--------------+

+------------+
|  Service   |  ← 继承 ContextWrapper(不带 UI Theme)
+------------+

BroadcastReceiver 没有继承 Context,它只是:

void onReceive(Context context, Intent intent)

通过参数暂时拿到一个 Context

1.3 关键类逐个看:

1) ContextImpl (内部实现)

  • 位于 framework 内部,是 Context 的具体实现类。(不对外暴露)

  • 持有:

    • LoadedApk / Resources / AssetManager / PackageInfo
    • IBinder 到 AMS 等系统服务的引用
  • 应用进程****中真正干活的对象,其他对外暴露的 Context 大多是对 ContextImpl 的包装。

2) ContextWrapper

  • 继承 Context,内部有一个:protected Context mBase;
  • 所有方法基本都是:
@Override
public Resources getResources() {
    return mBase.getResources();
}
  • 作用:利用委托模式包装一个 Context 实现(通常是 ContextImpl ),并允许子类按需重写部分行为。

3) ContextThemeWrapper

  • 继承 ContextWrapper引入主题( Theme )的概念

  • 维护:

    • Resources.Theme mTheme
    • int mThemeResource
  • 为 UI 组件(主要是 ActivityDialog 等)提供:

    • setTheme(int resId)
    • getTheme()
  • 只有基于 ContextThemeWrapper 的 Context 才真正支持 UI 主题。

4) Application

  • 继承 ContextWrapper全应用级别的 Context

  • 生命周期:onCreate() 在整个应用进程创建时调用一次。

  • 主要职责:

    • 初始化应用级别的组件(例如:网络库、日志系统、依赖注入容器等)
    • 提供 Application ContextgetApplicationContext()
  • 不直接参与 UI 绘制,对主题支持有限(不用于 View inflation 的 UI Theme)。

5) Activity

  • 继承 ContextThemeWrapper,因此:

    • 既是一个 Context
    • 又是一个 支持主题的 UI 宿主组件
  • 职责:

    • 管理界面(布局、交互)

    • 管理生命周期(onCreate/onStart/onResume…)

    • 承担一个“UI 级 Context”:用于

      • LayoutInflater
      • Dialog、Popup、Menu 的创建
      • 主题化资源加载(?attr/colorPrimary 等)

6) Service

  • 继承 ContextWrapper不支持 UI 主题

  • 职责:

    • 在后台执行长时间任务(播放音乐、网络下载等)
    • 支持与其他组件绑定/通信(bindService
  • 由于不处理 UI,不能直接显示窗口、Dialog(需要借助 Activity 或特殊系统权限)。

7) BroadcastReceiver

  • 不是 Context,但 onReceive() 会给你一个临时的 Context

  • 这个 Context 通常是一个 短生命周期的 ContextImpl 包装,主要用于:

    • 简单操作:startService()goAsync()
    • 不推荐做耗时操作(onReceive 要尽快返回)

1.4 类职责对比(简表)

类/角色是否继承 Context是否支持 Theme典型使用场景
Context(抽象)取决于子类框架统一接口,不直接使用
ContextImpl是(内部)视类型而定真正实现所有 Context 功能
ContextWrapper对 ContextImpl 的通用包装
ContextThemeWrapper提供 UI 主题能力(Activity、Dialog 等)
Application有方法,但不用于 UI全局初始化、单例、Application Context
Activity界面、View、用户交互
Service后台任务、长期运行逻辑
BroadcastReceiver被动响应广播,通过参数获取 Context

二、设计原理与机制

2.1 Context 作为“全局访问点”的设计思想

Android 不鼓励你使用大量的 static 全局变量来到处传递状态,而是通过 Context 作为 “应用环境的访问门面(facade) ,提供:

  • 统一的访问入口:资源、系统服务、组件启动都从这里拿。

  • 隐藏实现细节:你不需要知道 ActivityManager 在哪一进程,只管 startActivity()

  • 分级的“全局”

    • Application 级:Application / getApplicationContext()
    • Activity 级:带 UI 的 Context
    • Service 级、Receiver 级:非 UI Context

这有点类似 Service LocatorFacade 模式:让上层代码依赖稳定接口,而不依赖具体实现

2.2 资源访问机制(Resources / Assets)

当你调用:

Resources res = context.getResources();
Drawable d = res.getDrawable(R.drawable.xxx, context.getTheme());

内部大致流程:

  1. context(可能是 Activity / Application)最终委托到 ContextImpl

  2. ContextImpl 内部持有一个 Resources 实例,关联当前

    1. 包名 / APK
    2. Configuration(语言、方向、dpi 等)
    3. AssetManager
  3. Resources 根据当前 Configuration + Theme 选择合适的资源版本(如 values-envalues-night 等)。

关键点:

  • ContextImpl 决定“去哪儿找资源”
  • Theme 会影响资源最终表现(例如:?attr/colorPrimary

2.3 组件启动机制(以 startActivity 为例)

context.startActivity(new Intent(context, DetailActivity.class));

简化理解:

  1. Activity / ApplicationContextImpl.startActivity()
  2. 内部通过 Instrumentation / ActivityTaskManager / ActivityManagerService 与系统进程通信(Binder)
  3. 系统进程根据 Intent / Manifest 信息,在合适的进程启动/复用 Activity 实例。
  4. Activity 创建时,被注入一个 ContextImpl + ContextWrapper 组合的 Context,并在 attach() 时绑定。

好处: 你的代码只需调用 Context 的 API,不需要直接依赖任何 system_server 内部类。

2.4 系统服务获取机制: getSystemService()

Object getSystemService(String name) {
    // 1. 先查看当前 Context 的本地缓存(Map)
    // 2. 如果是“真·系统服务”:
    //    - 通过 ServiceManager 拿 Binder(一次)
    //    - 转成对应的 Manager 类(如 LocationManager)
    //    - 缓存起来
    // 3. 如果是本地工具类:
    //    - 直接 new(如 LayoutInflater)
    //    - 缓存起来
}

区别:

  • 例如 LocationManager PowerManager

    • 内部持有 Binder 代理 → 调用时可能产生 IPC。
  • 例如 LayoutInflater

    • 是当前进程内的纯 Java 对象 → 不产生 IPC。

Context 的意义:你不用区分“这个服务是远程的还是本地的”,统一通过 getSystemService() 拿就行。

2.5 为什么设计成抽象类 + Wrapper,而不是一个单例?

原因主要是几个维度的解耦与可扩展性:

  1. 不同组件需要“不一样”的 Context 行为

    1. Activity 需要 Theme、Window、startActivityForResult
    2. Service 不需要 Theme,也不能直接弹 UI
    3. Application 需要“全局但不依赖任何具体 UI”的 Context

ContextWrapper / ContextThemeWrapper 即可在保持统一接口的同时,添加/修改特定行为。

装饰者设计模式

  1. 内部实现可以替换

    1. 对外只暴露 Context 抽象类和 getSystemService() 这类稳定接口
    2. 内部 ContextImpl 的实现可以随着版本演进自由修改
  2. 方便测试 / Mock

    1. 在单元测试中可以实现一个自定义 Context 或基于 ContextWrapper 的 mock,不需要运行真实的 Android 系统。

三、主题(Theme)与样式(Style)支持

3.1 Context 与 Theme 的关系

只有基于 ContextThemeWrapper 的 Context 才真正具备“UI 主题上下文”的语义,核心数据结构:

public class ContextThemeWrapper extends ContextWrapper {
    private Resources.Theme mTheme;
    private int mThemeResource;
}

Activity = ContextThemeWrapper + UI 能力,因此:

  • ActivityTheme 决定:

    • setContentView() 时 View 默认使用怎样的样式
    • ?attr/colorPrimary, ?attr/textColorPrimary 等主题属性的解析结果

Application / Service 的 Context 即使有 setTheme() 方法,也通常不用于 View 的主题化(因为不会直接 inflate 界面)。

3.2 setTheme() / getTheme() 工作原理(简化)

@Override
public void setTheme(int resid) {
    mThemeResource = resid;
    initializeTheme();
}

private void initializeTheme() {
    final boolean first = (mTheme == null);
    if (first) {
        // 1. 创建一个新的 Theme
        mTheme = getResources().newTheme();
    }

    // 2. 先根据父 Context(或 Application)继承一份基础 Theme
    final Theme parent = getBaseContext().getTheme();
    if (parent != null) {
        mTheme.setTo(parent);
    }

    // 3. 再把当前资源 id 对应的 style 叠加上去
    mTheme.applyStyle(mThemeResource, true);
}

@Override
public Resources.Theme getTheme() {
    if (mTheme == null) {
        // 如果还没 setTheme,就根据默认 theme 初始化
        mThemeResource = getApplicationInfo().theme; // Manifest 里配置的
        initializeTheme();
    }
    return mTheme;
}

关键点:

  • Theme 是对 Resources<style> 的运行时组合/叠加结果。
  • applyStyle() 可以多次调用,后面的 style 能覆盖前面的属性。
  • Activity 的 Theme 在 onCreate() 前就已准备好,setContentView() 时就会用这个 Theme 去解析布局里的 ?attr/xxx

3.3 Activity 与 Application 主题的继承与覆盖

Manifest 中常见配置:

<application
    android:name=".MyApp"
    android:theme="@style/AppTheme">

    <activity
        android:name=".MainActivity"
        android:theme="@style/AppTheme.Main"/>
</application>

继承链:

  1. Application 设置 android:theme="@style/AppTheme"

    1. 作为 应用内 Activity 的默认主题(除非 Activity 自己覆盖)
  2. Activity 设置 android:theme="@style/AppTheme.Main"

    1. Theme = 以 Application 的 Theme 为基础,再叠加 AppTheme.Main 的效果(具体取决于 style 的父子关系)。
  3. 如果某个 Activity 未显式声明 android:theme

    1. 使用 Application 的 theme 作为其 Theme。

运行时覆盖:

在 Activity 中可以:

@Override
protected void onCreate(Bundle savedInstanceState) {
    setTheme(R.style.AppTheme_Dark); // 必须在 super.onCreate() 之前调用更稳妥
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

也可以为某个局部 UI 创建一个带特定 Theme 的 Context:

Context themedContext =
        new ContextThemeWrapper(this, R.style.MyDialogTheme);
AlertDialog dialog = new AlertDialog.Builder(themedContext)
        .setTitle("Title")
        .setMessage("Hello")
        .create();
dialog.show();

这个 ContextThemeWrapper 就是一个局部“主题化 Context” ,很常用于 Dialog、PopupMenu、RecyclerView item 的样式等。

四、使用建议与最佳实践

4.1 如何选择合适的 Context?

场景/需求推荐 Context 类型原因说明
加载/绘制界面(setContentView、inflate View)Activity Context (this)需要使用 Activity 的 Theme、Window,支持 UI 属性解析
创建 Dialog、PopupWindow、MenuActivity Context / 主题化 ContextThemeWrapper需要依附 Window、Theme,Application Context 无法正常工作
启动新的 Activity一般用 Activity Context与当前 Activity 的任务栈、动画等直接相关
启动或绑定 Service任意合法 Context(注:有时需要非 Activity)通常用 Application 或 Activity,都可以,与 UI 无强绑定
注册全局单例、工具类初始化Application Context生命周期 = 应用进程,不易造成 Activity 泄漏
访问系统服务(不依赖 UI,例如 Vibrator、Connectivity)Application Context仅需系统服务,不需要 Theme/Window
BroadcastReceiver 中进行短时操作onReceive 里的 Context 参数系统给你的就是合适的 Context,短时间用完即可
长时间缓存 Context 引用Application ContextActivity Context 可能被销毁,缓存会导致内存泄漏

经验总结:

  • 涉及 UI / Theme / View 的,一律优先用 Activity Context
  • 要放进单例、静态变量、长生命周期对象,则用 Application Context

4.2 避免内存泄漏的常见方法

  1. 不要把 Activity Context 存入静态变量 / 单例
public class ImageLoader {
    private static ImageLoader sInstance;
    private Context mContext;

    private ImageLoader(Context context) {
        mContext = context; // 若传入 Activity,则可能泄漏
    }

    public static ImageLoader getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new ImageLoader(context);
        }
        return sInstance;
    }
}

正确做法:改用 Application Context

mContext = context.getApplicationContext();
  1. 避免 Handler / Thread 等隐式持有 Activity
  • 非静态内部类会默认持有外部类(Activity)的引用。
  • 可以使用 静态内部类 + WeakReference<Activity>
static class MyHandler extends Handler {
    private final WeakReference<Activity> activityRef;

    MyHandler(Activity activity) {
        activityRef = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        Activity activity = activityRef.get();
        if (activity == null || activity.isFinishing()) return;
        // 安全使用 activity
    }
}
  1. 注意 View 持有 Context
  • 所有 View 的构造函数都会传入一个 Context,通常是 Activity。
  • 如果你把一个 View(或包含它的对象)缓存到单例里,相当于间接缓存 Activity Context → 泄漏。
  • 规则:不要把 View 放到长生命周期的静态集合或单例中。
  1. 使用生命周期感知组件(Lifecycle-Aware)
  • 使用 Jetpack 的 ViewModelLiveDataLifecycleOwner 等,让框架帮助你在适当时机清理引用。
  • ViewModel 中一般只用 Application Context(通过 AndroidViewModel),不要持有 Activity Context。

五、整体小结

  • Context 是 Android 应用环境的“门面”,统一对外提供资源访问、组件启动、系统服务获取等能力。

  • 通过 ContextImpl + ContextWrapper + ContextThemeWrapper 的分层设计

    • 把复杂、易变的内部实现封装在 ContextImpl 中;
    • 通过不同包装类(Application、Activity、Service)组合出不同语义的 Context;
    • 同时支持 UI 主题(Theme)、非 UI 背景服务、多进程等场景。
  • Theme/Style 与 Context 强绑定:只有基于 ContextThemeWrapper 的 Context 才具备完整的 UI 主题语义;Activity 通过 Manifest 与 setTheme() 决定自己的外观。

  • 实战中最关键的两点:

    • 搞清楚当前手上的 Context 属于哪一类(Activity / Application / Service 等),能做什么、不能做什么。
    • 长生命周期用 Application Context,短生命周期/UI 相关用 Activity Context,避免“误用 Context”导致的内存泄漏和 UI 问题。