所有 Android 开发者每天都会遇到 Android Context,即便是最基本的程序也是如此。但由于在不同情况下出于不同原因使用 Context
可能会让人感到困惑,理解它并不容易,因此,许多开发者对它有不同的理解。
因此,许多开发者在需要时会直接通过 Application
或 Activity
对象传递一个上下文对象,而不会深入考虑,因为这样代码能编译并通过;这样可能会导致内存泄漏和致命的应用崩溃,因为不当使用了 Context。
对上下文是什么以及为什么要用它的模糊认识,让开发者看不到代码和集成库的整体情况。
但围绕上下文的许多困惑始于没有意识到Manifest和Context之间有多么紧密的联系,而通过查看两者,我们可以更好地理解应用是如何与Android系统结合的。
本文将从底层 Android 架构的角度分析 Manifest 和 Context,以便读者能够从应用程序的角度理解它们的语义上的含义。
为此,本文将故意避免具体的代码细节,而是讨论Context
真正代表什么,以及它试图解决的问题,因为理解应该先于学习,而不是反过来。
相关的视频链接:视频
Android系统和应用当你安装一个应用时,它会被分配一个独一无二的Linux用户ID(UID),并在自己的Android运行时(ART)环境中运行。因此,每个应用都是完全独立的,其所有资源对其他应用都不可见。
当你运行一个应用程序时,它也不能直接通过代码来构建或管理自己的组件(如活动、服务等)。相反,它需要请求 Android 系统代为完成这些任务。
这种方法确保应用程序更加安全、结构更清晰。错误更少的原因在于,大多数的责任转嫁给了操作系统,而不是由开发人员的代码来处理。
但如果 Android 对应用限制很严格,就需要解决两个问题。
- 应用需要一种与Android系统通信的方法,这样应用就可以请求系统构建或提供无法直接通过文件系统或代码访问的组件和资源。
- Android系统应记录所有已安装的应用及其组件,以便在有匹配请求时启动或提供给调用者。
为了应对上述问题,Context 和 Manifest 分别登场,正如你看到的,它们之间有明显的联系。
这一切又是怎么凑到一起的呢?- 声明 是关于应用如何向操作系统声明可使用的具体功能组件
- 上下文 是应用如何与系统“沟通”以获取所需功能
注:原文中的Markdown样式已保持一致。
所以,每次Context做重要事情时,它通常引用操作系统通过Manifest了解的资源或组件。
应用声明当一个应用刚开始创建时,除非这些信息被写入 [**AndroidManifest.xml**](https://developer.android.com/guide/topics/manifest/manifest-intro)
文件,否则 Android 系统对它一无所知。
要想在应用抽屉里显示应用的图标,就需要在应用程序清单文件中通过隐式意图声明,将活动配置为启动活动(使用一个[intent-filter](https://developer.android.com/guide/components/intents-filters#Receiving)
)。
你定义的内容位于 [AndroidManifest.xml](https://developer.android.com/guide/topics/manifest/manifest-intro)
文件的 <manifest>
标签中。
- 应用包信息,用于操作系统将应用添加到Android软件栈的应用层,并分配一个唯一的用户标识,以便应用能够以该用户身份运行,这样应用在安装后就可以被访问到。
- 权限,使用
[<permission>](https://developer.android.com/guide/topics/manifest/permission-element)
标签让Android知道在应用执行过程中哪些受限的硬件和软件资源应该被启用。 - 以及任何自定义的四大组件的实现,让Android在请求时提供它们。
- 例如
[<activity>]
用于活动,
[<service>]
用于服务,
[<provider>]
用于内容提供者,和
[<receiver>]
用于广播接收器。
声明在清单文件中的四种组件类型在Android的进程间通信(IPC)基础设施中都发挥着关键作用。
进程间通信 (IPC) 及配置核心上,Android 使用了一个专门为 Android 打造的 Binder 框架 — 一个轻量级的 远程过程调用 (RPC) 系统,特别为 Android 优化。当你的应用与系统服务或其他应用通信时,实际上是通过 Binder IPC 完成的。
正如我们将看到的,Manifest的主要作用是注册IPC组件,使Android操作系统能够根据上下文的生命周期启动并管理它们,或在给定的上下文中使用它们。
所以,当你在清单中声明一个组件时,你实际上是将其注册到系统里,以便可能的跨进程交流。
The **android:exported**
属性(自 Android 12 起对于具有意图的组件是必需的)明确控制一个组件是否可以被其他应用程序访问,或者是否保持封装在您的应用中。
比如说:
- 导出的Activity 可以通过Intent被其他应用启动或调用
- 导出的服务 可以被其他应用启动或绑定
- 导出的内容提供者 使其数据对授权的应用可见
- 导出的广播接收器 能够接收来自系统或其他应用的广播
但是为什么Android需要在清单文件中声明这四种类型的子类呢?
让我们从_________意图(intents)开始,
注:链接中的术语“intents”在中文技术文档中通常直接翻译为“意图”,但保持了英文术语的链接形式。
调整为:
让我们从意图(intents)开始,
注释已移除,确保术语使用的一致性及简洁性。
明确意图当你从某个Activity 显式地打开另一个 Activity 时,你从不直接创建新的 Activity 对象并将变量传递给其构造函数,时。
(Note: There's an extra comma at the end which is not in the source text, but according to the expert suggestions, a comma should be added after "时" for readability and adherence to Chinese punctuation rules. However, an extra "时" is present at the end, which is not in the source text and should be removed.)
Corrected version:
当你从某个Activity 显式地打开另一个 Activity 时,你从不直接创建新的 Activity 对象并将变量传递给其构造函数。
实际上,通过明确的Intent来请求Android创建并启动目标Activity(通过其类名指定),并传递一个包含数据的Bundle对象(而不是传递构造函数参数)。
创建一个 Intent
对象(意图对象),用于从当前活动启动下一个活动 NextActivity
。然后调用 startActivity
方法来启动该 Intent
。
在这里,系统明白你想要NextActivity
,并且因为NextActivity
在清单文件中有记录,知道如何启动并运行这个NextActivity
。如果NextActivity
没有在清单文件中声明,系统将无法启动这个NextActivity
。
因为在安装应用程序时,Android 操作系统已经按包名和类名索引了所有组件,这要归功于清单声明,所以它知道通过类名找到你请求的那一个组件,并代表调用者构造和启动它。
隐含意图另一方面,当你使用一个[隐式意图]时,你其实并不知道哪个应用组件会处理这个请求。
Android系统会检查所有已安装应用的Manifest声明,以确定哪些活动(或其他组件)可以处理你的意图,这些活动符合请求的intent-filter
。
这就是像“分享”或“使用其他应用打开”这样的功能如何触发其他应用的。再一次,系统会通过每个应用的清单来决定使用哪个组件来处理请求。
数据包在意图中使用 Bundle
传递数据是必要的,因为操作系统处于中间,需要一种标准的方式来序列化和反序列化数据。
这就是为什么只允许特定的数据类型。Android 必须确保它能一致地处理这些数据类型。
Bundles 用于通过 Android 系统在不同应用之间传递数据,也是一种进程间通信工具。
待办事项PendingIntent(https://medium.com/androiddevelopers/all-about-pendingintents-748c8eb8619)在功能如通知、小部件、闹钟,或通过WorkManager安排未来任务中起着关键作用。你可以使用应用的上下文来创建一个 PendingIntent
:`
val intent = Intent(context, TargetActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE // 或 FLAG_UPDATE_CURRENT 等
)
操作系统稍后可以像你的应用程序发起一样“启动”这个Intent,包括以你的应用程序的身份和权限。如果TargetActivity
没有在清单文件中声明,系统即使通过待处理的Intent也无法启动它。从Android 12开始,你必须声明FLAG_IMMUTABLE
或FLAG_MUTABLE
来说明PendingIntent出于安全考虑可以如何被修改。
清单中的应用组件Intent 是 Android 中用于请求操作系统构建并分发应用程序组件的一种机制(比如启动其他活动、服务或发送广播)等等。
- 活动 &清单:Android 操作系统了解所有活动,因此可以针对明确或隐含的意图请求来创建和启动它们。
- 服务 &清单:Android 了解所有服务,因此知道哪些后台操作它可以执行。
- 内容提供者 &清单:当需要时,可以向你的应用和其他应用提供内容。
- 广播接收器 &清单:Android 了解所有通过清单文件注册的接收器,因此即使应用未运行,注册到特定广播的接收器也会收到通知。
关于接收器的小贴士 :
在清单文件中定义接收器并不是强制性的。应用可以通过“清单文件中注册的接收器”(通过清单文件中的[ _<receiver>_](https://developer.android.com/guide/topics/manifest/receiver-element)
标签)来接收广播,这种注册的接收器会一直监听广播,也可以通过运行时“动态注册的接收器”(通过[ _Context.registerReceiver()_](https://developer.android.com/guide/components/broadcasts#context-registered-receivers)
_方法)来接收广播,这种接收器仅在需要时监听广播。
什么是AndroidManifest?简述当这四个应用组件在清单文件中声明时,让 Android 系统知道它们的存在,这样当你的应用或其它应用的某些请求与它们的功能匹配时,无论是明示还是暗示,系统都会相应地作出反应。
《声明文件》是应用向系统发出的“说明”:
- 让操作系统知道你的应用程序能做什么,它包含哪些组件,以及它需要哪些权限。
- 它为应用程序之间的交互提供了基础,通过
intent-filter
或 内容提供者,你的应用可以通过这些组件与其他应用互动,反之亦然! - 现代 Android 版本对组件是否可被外部访问有更严格的要求。如果你在 Android 12 及以上版本中忽略了
android:exported
等配置,你的应用可能无法安装或出现异常行为。
在讨论Context之前,我们应当明确,并非所有的manifest条目都指向Context的子类这一点。
蓝色:上下文组件 — 绿色:非上下文组件
上下文组件这些表现出来的组件继承自 Context
类,并执行UI操作或后台任务。它们由操作系统直接创建并启动,因此它们有一个由操作系统管理的生命周期。
这些明显的组成部分是:
- 应用
- activity
- 服务
内容提供者和广播接收器是非Context
类的子对象,然而,它们需要一个上下文(context
),因为它们的功能依赖于Android操作系统环境。
- 内容供应商,
你可以通过从应用程序的
[_Context_](https://developer.android.com/reference/android/content/Context)
获得的[_ContentResolver_](https://developer.android.com/reference/android/content/ContentResolver)
对象来访问内容提供者中的数据。
这行代码的作用是通过内容解析器查询 MY_CONTENT_URI
的内容。
ContentResolver
属于你的 应用的上下文环境。提供程序本身则通过清单进行识别,而实际的查询总是通过一个 Context
来传递,以获取所需的内容。
- 广播接收器
来源:developer.android.com._
用于接收并处理通过Context.sendBroadcast(Intent).
发送的广播的基础类的代码.
即当广播发生 (例如,来自系统或其他应用调用 sendBroadcast(intent)
) 时,操作系统会通过清单文件或运行时注册来确定哪些接收者应处理它。实际的方法签名是 onReceive(Context context, Intent intent)
,表明广播是通过上下文引用传来的。如果应用在后台运行,系统可能会启动一个进程,附上下文,传递广播。
让我们从官方的“Context”定义开始,即使它一开始可能看起来有些奇怪,到文章结束时,你会发现这一切都很合理。
提供关于应用环境的全局信息的接口。这是一个由 Android 系统实现的抽象类。它允许访问应用特定的资源和类,以及执行应用级别的操作,如发送和接收广播等。
理解清单后,你意识到在Android系统承担了这么多任务的情况下,你的应用需要一个与Android操作系统交互的接口。这个接口以Android上下文的形式存在。
抽象类及其子类这是一个由Android系统提供的抽象类。
根据 Google 的定义,context 是一个 抽象类,这意味着你无法直接使用任何 Context 对象,但你可以找到其子类,例如 Application
、Activity
、Services
等。这些子类包括。因此,在处理这些类时,你实际上就是在与一个 Context 交互。
而且由于 Context 的实现是由 Android 提供的,因此,为了创建和初始化 Context 对象,需要通过操作系统管理的机制,例如 intents,而不是直接使用经典的构造函数。
**Activity**
是一个专门用于用户界面任务的Context
。**Service**
是一个专门用于后台操作的Context
。**Application**
是整个应用的全局上下文,对于需要跨活动持久化的事项(例如数据库管理)非常有用。
但如果是 Android 提供了 Context 实现,那表示Context 可以作为你的 App 和 Android 之间的桥梁,从而作为通往 设备的全局信息 的窗口。
应用程序环境全局信息接口
就像你的手机使用的是浅色或深色主题,是竖屏或横屏模式,但也支持与全局设置的无缝融合,比如允许 Android 在自动将正确的字体大小应用于自定义的 TextView
时,结合设备的全局字体大小设置与你的自定义 SP 值,在布局 inflate 时应用该设置,或者返回资源值的正确变体。
它提供访问应用程序专有资源和类的途径
是的,资源内容也是通过上下文来获取Android系统提供的,而不是通过应用程序的直接代码。Android使用基于键值对的机制通过上下文来提供资源,该机制用来请求资源。
资源,例如字符串、尺寸、布局,都来自项目中的 res/
文件夹,但你从不直接访问文件。相反,你使用 context.getResources()
或 context.getString()
。Android 可以自动返回合适的语言/屏幕密度版本。这种方法从Android早期版本开始就一直保持一致,尽管在新版本中会引入新的资源限定符(例如新的屏幕尺寸或可折叠设备类别等)。
这不仅安全,而且智能,因为Android系统甚至可以根据全局设置,例如设备的语言偏好来决定返回不同资源值的变种,例如根据设备的语言偏好返回适当的字符串资源值,或者根据屏幕的像素密度和大小返回合适的图像变种。
哪里可以找到背景!所以,Android 的 Context 会将你的应用和 Android 系统绑定起来,以便完成或获取那些必须通过 Android 系统才能检索的内容
由于 [Activity](https://developer.android.com/reference/android/app/Activity)
、 [Service](https://developer.android.com/reference/android/content/Context)
和 [Application](https://developer.android.com/reference/android/app/Application)
这些类都是 [Context](https://developer.android.com/reference/android/content/Context)
类的子类,因此,如果你在一个 Activity 的方法中,this
就是你当前的 Activity 上下文。在 Service 中,this
是服务的上下文。如果你想获取全局引用,你可以通过调用 getApplicationContext()
来获取全局的 Application 上下文。
然而,这些上下文有不同的生命周期。
- Activity上下文仅存在于
onCreate()
和onDestroy()
之间。 - Application上下文则在整个应用进程存活期间都存在。
但由於上述類有不同的生存周期,例如將 activity 的 context 作為整個應用的 Android 引用傳遞,這可能會導致 内存泄露,導致 Activity(及其所有视图和资源)无法被垃圾回收。
Android上下文环境:选择合适的类型以应对不同的工作 大问题来了!为什么不总是使用 ApplicationContext,因为我们只需要一个对 Android 的引用,不用操心内存泄漏?为什么要用不同生命周期的 Context?
理解上下文类型归结于三个关键因素:继承结构、能力以及生命周期的含义。
上下文继承特性和功能Activity
继承自 [ContextThemeWrapper](https://developer.android.com/reference/android/view/ContextThemeWrapper)
,而 Service
和 Application
则没有这种继承关系。这种继承的区别非常重要——这个名字中的“Theme”揭示了它的用途。ApplicationContext
缺少 UI 能力,无法加载布局文件、启动 Activity 或以正确的主题显示对话框或对话。
使用 ApplicationContext
执行 UI 操作时通常会引发类似“无法添加窗口 -- token null 无效”的错误,比如显示对话框时。
不同的上下文提供对应用程序环境不同部分的访问。Activity
上下文信息包括主题信息、窗口特性和访问 UI 线程。当两个活动使用不同的主题(亮/暗)时,尽管它们访问相同的资源,但因为使用不同的主题(亮/暗),它们对主题属性的解释也会有所不同。
ApplicationContext
提供了应用程序级别的访问,但不了解 UI 状态或当前主题属性,因此不适合用于需要根据特定主题资源进行解析的 UI 操作。
对于那些跨越单一屏幕生命周期的非UI组件(例如数据库访问、网络客户端或单例),ApplicationContext
能防止内存泄漏。当长期存在的对象持有 Activity
的上下文引用时,它会阻止该 Activity
在销毁时(例如在配置更改期间)被垃圾回收。
在这些情况下使用 ApplicationContext
可以确保引用的是贯穿应用程序整个生命周期的应用程序范围的上下文环境。
Context在方法调用中作为参数的角色结束语:始终根据目的选择上下文。
_活动
_上下文用于UI操作,_ApplicationContext_
用于长生命周期组件。这种方法确保功能正常运作,并防止内存泄漏——这是Android架构中的基本原则。
在Android中传递上下文于方法调用中非常常见。然而,由于传递上下文的原因可能各不相同,这使开发人员对上下文的作用变得模糊不清。
因此,当你在方法调用中加入上下文时,上下文可能扮演以下三种角色中的一个:
- 被动角色:向操作系统请求资源的值是多少 (例如:资源的值是什么)。
- 主动角色:请求操作系统帮你做某事 (例如:通过Intent启动一个组件,加载布局或界面,显示一条消息等)。
- 绑定角色:让你的应用与操作系统管理的远程实体或机制连接,在这种情况下,绑定将在“主动”和“被动”角色中使用,以读取或更新数据库中的数据——你几乎总是会使用Application Context,因为绑定应该在整个应用的生命周期中存在。
无论怎样,上下文环境始终是位于你的代码和Android系统之间的操作系统中间层,负责传递请求以获取信息或执行操作。
所以当你在方法调用中使用“上下文环境”时,你就会知道它是一个会传递到操作系统并请求执行你方法请求的媒介。
上下文环境作为生成组件的角色是怎样的?除了在方法调用中传递Context之外,Activity
和 Service
本身也是 Context
对象或组件。
它们不会通过普通的构造方法创建;Android 系统通过 intents
来创建它们。因此,调用 new MainActivity()
从来不会触发 onCreate
方法。
操作系统需要启动这些活动的生命周期来管理它们的生命周期,如onCreate
、onStart
、onResume
等。
Android 的平台架构
组件的作用:代表自定义可启动组件的角色。这些自定义可启动组件属于软件堆栈中的应用层,但它们扩展或使用了应用框架中定义的类,因此它们充当了应用与操作系统之间的桥梁。
通过 Android 操作系统中的“意图”等机制被创建和启动的这些 Context 组件,会获得由操作系统管理的 生命周期管理。
例如,对于一个继承了 Activity 类的类,如果你尝试通过常规方式(MyActivity ma = new MyActivity();
)实例化它,onCreate()
方法不会被自动调用。只有通过 Intent 启动 Activity 时,该方法才会被调用(来源)。
因此,作为对象的Context的作用是,让新构建的子类组件(如Activity)能够与Android系统对接,从而让系统管理并更新该组件的生命周期。
Android Context 是什么 — TL;DRContext(Context)是 Android 提供的一个类,它的作用是连接你的应用代码与 Android 系统之间。通过你的 Application 类以及从 Context 继承的其他自定义组件,你的应用可以通过这些组件访问操作系统独有的资源和功能。
当操作系统通过创建机制(如“意图”)生成这些类的对象时,这些对象将被操作系统管理,并因此拥有生命周期。
对于其他任何事情,通过将上下文作为参数传递到方法调用中,这样方法就可以利用上下文与操作系统进行通信,从而让操作系统执行某些操作或返回某些资源或信息。
聊聊单一活动架构:在现代 Android 中的上下文与生命周期 单任务应用的兴起Android开发的发展历程已经从多Activity应用向单Activity架构发生了显著的转变。采用这种方法,开发者不再为每个屏幕创建一个新的Activity,而是将整个应用的UI构建在一个单一的Activity中。
这种根本性变化引发了关于只有一个Activity作为整个应用基础时,Context和生命周期管理如何工作的重要问题。
含 fragment 的单活动应用:第一步:踏上 fragment 之旅在 Jetpack Compose 出现之前,开发人员使用 Fragments 和 XML 布局来创建单 Activity 应用。在这种情况下,应用的清单文件中只会声明一个 Activity,并且这个 Activity
将作为承载不同屏幕 Fragment 的容器:
// manifest 中声明的唯一 Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 显示 Fragment 的容器区域
if (savedInstanceState == null) {
// 若 savedInstanceState 为空,则替换 R.id.container 为 HomeFragment
supportFragmentManager.beginTransaction()
.replace(R.id.container, HomeFragment())
.commit()
}
}
}
尽管只有一个Activity
,Context系统依然非常重要。由Android系统创建的单个Activity
仍然为操作系统提供Context
桥梁。所有Fragment进行UI操作时共享这个Activity的Context
,包括加载布局和访问资源。Fragment有自己的生命周期方法,但这些方法与父Activity的生命周期紧密相关,受其协调。
Jetpack Compose 进一步推动了单活动架构,完全消除了对 Fragment 和 XML 布局的依赖。用 Compose,开发人员通过 可组合函数 而不是 XML 文件来定义用户界面:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
// Compose内部处理导航
MainNavHost() // 这里用MainNavHost替代了Fragment的导航
}
}
}
}
然而,底层的 Context 系统并不会消失——它只是改变了访问和使用它的方式。在 Compose 应用中,清单中仍然至少声明了一个 Activity
。这个 Activity
调用 setContent()
而不是 setContentView()
,以此确立 Compose 作为 UI 框架。Activity
继续充当应用与 Android 系统之间的桥梁,提供许多操作所需 Context。
当在单活动架构中使用Compose时,上下文流仍然很重要,但不再那么显眼。Activity的上下文会通过Compose系统传递下去,并可通过LocalContext
可组合项获取。
@Composable
fun ScreenWithSystemAccess() {
// 获取当前 Activity 的 Context
val context = LocalContext.current
Column {
Button(onClick = {
// 使用 Context 显示一个 Toast 消息
Toast.makeText(context, "Hello", Toast.LENGTH_SHORT).show()
}) {
Text("显示 Toast")
}
// 访问应用资源
Text(text = context.getString(R.string.welcome_message).toString())
}
}
许多Compose函数在构建基本UI元素时不需要直接使用Context,使得这种依赖关系变得不那么明显。然而,像显示Toast、访问资源或使用系统服务这样的操作仍然需要Context。
此上下文来源于Activity,保持了Android的基本架构,其中Context作为与Android系统通信的桥梁,
Compose 中的生命周期处理要理解Compose应用程序的生命周期,需要弄清楚Compose自身的生命周期概念是如何与Activity
生命周期互动的。
Compose 引入了 组合生命周期概念 — UI 元素在出现时会进入组合状态,在消失时会退出组合状态。由 Compose 运行时来处理,这与 Activity 的生命周期有所不同。
不过,Activity 的生命周期仍然很重要。由系统创建的 Activity 仍然会接收到诸如 onCreate
、onResume
、onPause
和 onDestroy
这样的生命周期回调。您可以通过 [LocalLifecycleOwner](https://developer.android.com/topic/libraries/architecture/compose)
在 Compose 中观察这些回调。
@Composable
fun ScreenWithLifecycleAwareness() {
// 这样我们就可以获取 Activity 的生命周期
val lifecycleOwner = LocalLifecycleOwner.current
// 我们可以观察 Activity 的生命周期事件
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> {
// 当 Activity 恢复时做一些事
}
Lifecycle.Event.ON_PAUSE -> {
// 当 Activity 暂停时做一些事
}
}
}
// 将观察者注册到生命周期
lifecycleOwner.lifecycle.addObserver(observer)
// 当此可组合项不再使用时进行清理
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
这意味着虽然 Compose 有自己的组合生命周期,但它不会取代 Activity 的生命周期。而是两者相互协作。Activity 从系统接收生命周期事件,并可通过 LocalLifecycleOwner 将这些事件传递给感兴趣的可组合项。
状态管理 与生命周期感知(状态管理和生命周期感知分别指处理和管理状态以及感知和响应组件生命周期的改变)当我们在查看Compose如何处理状态的收集时,上下文、生命周期和状态之间的联系在这一点上变得特别清晰。Compose提供了两种主要的方法来从Flows收集状态:collectAsState
和 collectAsStateWithLifecycle
:
@Composable
fun 用户资料界面(viewModel: ProfileViewModel) {
// 基本状态 - 在后台持续更新
val 基本状态 = viewModel.basicStateFlow.collectAsState().value
// 生命周期感知状态 - 在后台暂停更新
val 生命周期感知状态 = viewModel.importantStateFlow
.collectAsStateWithLifecycle().value
// 在UI中利用这些状态...
}
这两个函数之间的区别在于生命周期感知。 基本的 collectAsState
函数会在可组合项构建时从 Flow 中收集数据,无论是应用在前台还是后台。相比之下,collectAsStateWithLifecycle
函数会根据 Activity 的生命周期来调整收集行为——当 Activity 被切换到后台时会暂停收集,而当 Activity 返回到前台时会继续收集。
这种生命周期感知型集合之所以可能,是因为Compose可以通过LocalLifecycleOwner组件访问Activity的生命周期。该函数利用Activity的生命周期事件来高效管理Flow集合,展示了即使在基于Compose的UI中,Activity的上下文也继续影响应用程序的行为。
上下文的重要性持续存在尽管 Android 开发正朝着更现代化的方法如单一活动架构(Single Activity 架构)和使用 Compose 的声明式 UI 发展,Context 系统仍然是核心。Context 仍然作为应用与 Android 系统之间的基本桥梁,提供对资源、服务和生命周期事件的访问。
我们与这个系统交互的方式已经发生变化。在多Activity的应用中,每个Activity都提供了自己的Context。在单活动应用中使用Fragments时,一个Activity的Context会在多个Fragments中共享。在使用Compose的应用中,Activity的Context是通过如LocalContext和LocalLifecycleOwner这样的可组合环境对象提供。
这种演变表明了Android的基本架构不断适应的同时保持其核心原则。理解Context和生命周期管理在这些现代方式中的作用,可以帮助开发者开发出能高效运作的Android应用,不论选择何种UI框架或架构。
可视化:清单文件及其上下文环境每条线都是一个将应用层与应用框架连接的上下文环境,在此过程中为连接组件与Android系统框架之间开启一个通道。
一个好比来想象“展示”和“环境”的概念,可以是老式电话交换台:
- 基础是应用框架,这里所有的线都从这里延伸出来,将每个应用程序组件与Android系统连接起来。
- 每个应用通过其清单声明向Android系统暴露插孔,使系统能够构建这些组件并插入上下文线进行管理。
- 最后,每根线是构建的可启动组件中的Android上下文部分,将应用组件与系统绑定在一起。
所以清单文件在软件栈的应用层添加了应用程序,并为每个应用及其组件创建了插口。当Android系统构建一个继承Context的组件时,会自动将Context接口连接到新构建组件的插口,这样操作系统就可以链接并管理该组件。
假设当组件被销毁时,其连接的线会被断开。而当另一个组件被创建时,一个新的线会连接到相应的清单中声明的插孔。
测试及设计模式现在你明白了上下文存在的意义,你就能体会到单元测试和带仪器的测试之间的差异。
简单来说,单元测试无需与 Android 系统交互,直接在 JVM 上运行测试,而集成测试则要在代码与 Android 系统交互的基础上进行,因此它们必须在模拟器或真机上运行,这使得它们比本地单元测试要慢且更麻烦得多。
所以,通常需要使用带仪器的测试,当需要深入操作操作系统,并且显然需要上下文环境时。或者当你直接测试自定义组件(比如 Activity,它是 Context 的一个子类)时也是如此。之类的
(作为旁注,在一些简单的情况中,如果你只是使用一个不涉及操作系统深度介入的环境,你可以模拟上下文并避免运行带有仪器的测试——但这已经超出了本文的讨论范围)。
考虑到这一点,如果我们使用像MVVM(模型-视图-视图模型)这样的设计模式,这种模式得到了Google的支持。你可以将所有的业务逻辑置于ViewModel中,并将这些逻辑与UI相关的代码和数据层分离,通过提供带有示例数据的模拟数据仓库。那么你的业务逻辑应该独立于上下文,这样你就可以使用本地测试而不是仪表测试来测试ViewModel,从而实现更快的测试,并且代码更清晰、更易于调试。
但是要分离业务逻辑,你需要明白你的代码何时涉及Android,何时不涉及。
所以,了解你的应用与Android系统是如何耦合的,可以帮助你更好地理解选择诸如设计模式(如MVC或MVVM)来实现职责分离的原因,也许还会改变你编写代码的方式!
在这里了解更多关于测试的信息: https://developer.android.com/training/testing/fundamentals
理解上下文更深层的意义正如我们在本文中所探讨的,Context不仅仅只是一个在Android开发中的API或参数,它体现了你的应用与Android操作系统之间的一种根本性关联。这种关系定义了Android开发的独特之处,使之区别于一般的编程。
考虑一下如果没有 Context: 如果没有 Context,每个测试都可以简单地作为单元测试运行,Android 实质上就变成了一堆基于 Java 虚拟机的库。你的应用就不再能被称为真正的“Android 应用”了——它仅仅是一个使用了一些额外库的 Java 或 Kotlin 应用程序,可以在任何安装了 JRE 的操作系统上运行的 Java 或 Kotlin 应用程序。
因此正是由于 Context,你的应用才真正算是一个“Android应用。” Context代表了连接你的代码与Android平台的桥梁,提供了所有系统交互所需的通道。没有这个连接,Android开发的独特性就会丧失。
共同学习,写下你的评论
评论加载中...
作者其他优质文章