如果我问你 Context,你扛得住吗?

1,244 阅读9分钟

本系列为小说《逆袭西二旗》的技术讲解,用于详细说明剧情里涉及的开发细节。

0.jpg

什么是 Context,Context 有哪些类型

Context 代表应用程序的环境或状态,提供对应用专属资源和类的访问。它充当应用与 Android 系统之间的桥梁,让组件能够访问资源、数据库、系统服务等。在执行诸如启动 Activity、读取 assets 文件或加载布局等任务时,Context 至关重要。

Android 中有多种类型的 Context。

面试问题

在 Android 应用中,为何使用正确的 Context 类型至关重要?
持有 Activit Context 的长生命周期引用又可能带来哪些风险?

Application Context

Application Context 与整个应用程序的生命周期绑定。当你需要一个全局、长生命周期且独立于当前 Activity 或 Fragment 的 Context 时,就应使用它。可通过调用 getApplicationContext() 获取。

适用场景:

  • 访问全局资源,如 SharedPreferences 或数据库;
  • 注册需贯穿整个应用生命周期的广播接收器;
  • 初始化在整个应用生命周期内持续存在的库或组件。

Activity Context

Activity Context(即 Activity 中的 this)与 Activity 的生命周期绑定,用于访问资源、启动其他 Activity,或加载该 Activity 特有的布局。

适用场景:

  • 创建或修改 UI 组件;
  • 启动另一个 Activity;
  • 访问限定于当前 Activity 的资源或主题。

Service Context

Service Context 与 Service 的生命周期绑定,主要用于后台任务,例如执行网络请求或播放音乐。它可访问 Service 所需的系统级服务。

Broadcast Context

Broadcast Context 在 BroadcastReceiver 被调用时提供。它的生命周期很短,通常仅用于响应特定广播事件。切勿在此上下文中执行耗时操作。

Context 的常见使用场景

  1. 访问资源:通过 getString()、getDrawable() 等方法,Context 可用于获取字符串、图片、尺寸等资源。
  2. 加载布局:使用 Context 通过 LayoutInflater 将 XML 布局文件转换为 View 对象。
  3. 启动 Activity 和 Service:调用 startActivity() 或 startService() 时必须传入 Context。
  4. 访问系统服务:通过 getSystemService(),Context 能获取如 ClipboardManager、ConnectivityManager 等系统级服务。
  5. 数据库与 SharedPreferences 操作:Context 用于访问 SQLite 数据库或 SharedPreferences 等持久化存储机制。

总结

Context 是 Android 中的核心组件,使应用能够与系统资源进行交互。Android 提供了多种 Context 类型,如 Application Context、Activity Context 和 Service Context,各自适用于不同场景。正确使用 Context 不仅能高效管理资源,还能避免内存泄漏和崩溃。因此,务必根据实际需求选择合适的 Context,并避免不必要的长期持有。

进阶:使用 Context 时要注意什么

Context 是 Android 中一种便捷的机制,但使用不当会引发严重问题,比如内存泄漏、崩溃,或者资源处理效率低下。为避免这些陷阱,务必了解其细微差别以及有效使用 Context 的最佳实践。

最常见的问题之一,是在 Activity 或 Fragment 等 Context 的生命周期之外,仍然保留对它们的引用。这会导致内存泄漏,因为垃圾回收器无法回收该 Context 及其相关资源所占用的内存。

例如,下面的示例代码可能会导致内存泄漏:

object Singleton {
    var context: Context? = null // If this retains the Activity context, causing a memory leak
}

相反,对于需要 Context 的长生命周期对象,应使用 Application Context:

object Singleton {
    lateinit var applicationContext: Context
}

因此,使用合适类型的 Context 至关重要。不同类型的 Context 用途各异,用错类型可能导致意外行为:

  • 执行与界面(UI)相关的任务,如加载布局或显示对话框时,使用 Activity Context。
  • 进行与 UI 生命周期无关的操作,如初始化库时,使用 Application Context。

下面的示例展示了误用 Application Context 的情况:

val dialog = AlertDialog.Builder(context.getApplicationContext()) // Incorrect

应使用 Activity Context 以确保正确的主题设置:

val dialog = AlertDialog.Builder(activityContext) // Correct

另一个关键考量点是,当关联组件(如 Activity 或 Fragment)被销毁后,要避免使用 Context。访问与已销毁组件绑定的 Context,可能会导致程序崩溃或出现未定义行为,因为与该 Context 关联的资源可能已不复存在。

下面的示例展示了对 Context 的误用,其中自定义视图的创建方式不当:

activity.finish() // The activity is destroyed
val text = TextView(activity) // but the TextView retains a reference

如果在 activity 销毁后依然使用 text,是非常危险的行为(例如 activity 销毁后,才请求到数据,然后更新 text 的显示)。

避免在后台线程使用 Context。

Context 是为主线程设计的,尤其适用于访问资源或与用户界面(UI)交互。在后台线程使用它可能会导致意外崩溃或线程问题。在与 UI 相关的 Context 资源交互前,需切换回主线程:

viewModelScope.launch {
    val data = fetchData()
    withContext(Dispatchers.Main) {
        Toast.makeText(context, "Data fetched", Toast.LENGTH_SHORT).show()
    }
}

总之。

要有效使用 Context,需谨慎考量,规避常见陷阱。不应在 Activity 或 Fragment 的生命周期结束后仍保留其 Context,否则可能导致内存泄漏。针对特定任务,务必选择正确类型的 Context,避免在后台线程,或关联组件销毁后使用它。此外,使用匿名内部类和回调时要小心,它们可能会不经意间持有对 Context 的引用。正确管理 Context 能确保高效利用资源,有助于防止内存泄漏或应用程序崩溃。

进阶:ContextWrapper

ContextWrapper 是 Android 中的一个基类,它具备包装 Context 对象的能力,将调用委托给被包装的 Context。它充当中间层,用于修改或扩展原始 Context 的行为。通过使用 ContextWrapper,你可以在不改变底层 Context 的情况下定制功能。

当你需要增强或覆盖现有 Context 的特定行为时,就会用到 ContextWrapper。它允许你拦截对原始 Context 的调用,并提供额外功能或定制化行为。

以 Activity 的继承关系为例:Activity -> ContextThemeWrapper -> ContextWrapper。

而 ContextWrapper 源码如下:

public class ContextWrapper extends Context {
    @UnsupportedAppUsage
    Context mBase;
    ...
}

使用场景:

  1. 自定义 Context:当你出于特定目的需要创建自定义 Context 时,例如应用不同的主题,或以特定方式处理资源。
  2. 动态资源处理:包装 Context 以动态提供或修改诸如字符串、尺寸或样式等资源。
  3. 依赖注入:像 Dagger 和 Hilt 这样的库会创建 ContextWrapper,以便将自定义 Context 附加到组件上进行依赖注入。

下面的代码展示了如何使用 ContextWrapper 来应用自定义主题:

class CustomThemeContextWrapper(base: Context) : ContextWrapper(base) {
    override fun getTheme(): Resources.Theme {
        val theme = super.getTheme()
        theme.applyStyle(R.style.CustomTheme, true) // Apply a custom theme
        return theme
    }
}

你可以在 Activity 中使用这个自定义包装器:

class MyActivity : AppCompatActivity() {
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(CustomThemeContextWrapper(newBase))
    }
}

在此示例中,CustomThemeContextWrapper 为 Activity 应用了特定主题,覆盖了基础上下文的默认行为。

这样做有很多好处:

  • 可复用性:你能将自定义逻辑封装在包装器类中,并在多个组件间复用。
  • 封装性:增强或修改行为的同时,无需更改原始 Context 的实现。
  • 兼容性:与现有 Context 对象无缝协作,保持向后兼容性。

总之。

ContextWrapper 是 Android 中用于定制 Context 行为的灵活且可复用的工具。它使开发者能够拦截并修改对原始 Context 的调用,而无需直接更改它,这使其成为构建模块化且适应性强的应用程序必不可少的类。

进阶:Activity 中的 baseContext 是什么

在 Activity 中,this 和 baseContext 都能用于访问 Context,但它们用途不同,代表着 Android 上下文层级结构中的不同层次。清楚何时使用它们,对避免代码混淆或潜在问题至关重要。

在 Activity 中,关键字 this 指的是 Activity 类的当前实例。由于 Activity 是 ContextWrapper 的子类(因而间接是 Context 的子类),this 提供了对 Activity 特定上下文的访问,包括诸如生命周期管理以及与用户界面(UI)交互等额外功能。

当在 Activity 中使用 this 时,它通常指向 Activity 的当前上下文,允许你调用特定于该 Activity 的方法。例如,如果你需要启动另一个 Activity 或显示与这个特定 Activity 相关联的对话框,就可以使用 this。

val intent = Intent(this, AnotherActivity::class.java)
startActivity(intent)

val dialog = AlertDialog.Builder(this)
    .setTitle("Example")
    .setMessage("This dialog is tied to this Activity instance.")
    .show()

baseContext 代表 Activity 所基于的基础上下文。它是 ContextWrapper 类的一部分,而 Activity 继承自 ContextWrapper。baseContext 通常是 ContextImpl 实例,为 Context 方法提供核心实现。

baseContext 一般通过 getBaseContext() 方法来访问。你很少直接使用它,但在处理自定义 ContextWrapper 实现,或者需要引用被包装上下文背后的原始上下文时,它就会派上用场。

// 和直接通过 this 返回的相比,此处的 baseContext 不携带当前 Activity 的主题信息
val systemService = baseContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE)

总结一下他们之间的区别:

  1. 作用域:this 代表当前的 Activity 实例及其生命周期,而 baseContext 指的是 Activity 构建所基于的更低层次的上下文。
  2. 用法:this 通常用于与 Activity 生命周期或用户界面(UI)相关的操作,例如启动 Activity 或显示对话框。baseContext 一般在与 Context 的核心实现进行交互时使用,特别是在自定义 ContextWrapper 的场景中。
  3. 层级关系:baseContext 是 Activity 的基础上下文。访问 baseContext 会绕过 Activity 作为 ContextWrapper 所提供的额外功能。

总之。

在 Activity 中,this 指向当前的 Activity 实例,提供了具有生命周期和特定于用户界面(UI)功能的高级上下文。而 baseContext 代表 Activity 所基于的基础上下文,常用于诸如自定义 ContextWrapper 实现等高级场景。虽然在 Android 开发中 this 使用最为普遍,但理解 baseContext 有助于调试以及创建模块化、可复用的组件。

番外

本文讲述的技术细节完全是基于面试下的,如果你想对 Context 有个更加完整的认识,请看下面两篇文章:

憋了一周了,12000字深入浅出Android的Context机制

2025年了,万字长文带你了解Context