Lifecycles
我们在应用中,经常需要感知 Activity 的生命周期。比如在某个界面中发起了网络请求,但当响应结果返回时,界面却关闭了,这时我们就不应该对响应结果进行处理,否则可能会导致应用崩溃。
在 Activity 内部感知其生命周期并不难,但我们常常需要在非 Activity 组件(如 ViewModel)中进行感知。
你可以通过在 Activity 中嵌入一个隐藏的 Fragment,或是通过手写监听器的方式来解决这个问题。例如,一个简单的监听器实现:
kotlin
// 手写监听器
class MyObserver {
fun activityStart() {
Log.d("MyObserver","MainActivity onStart()...")
}
fun activityStop() {
Log.d("MyObserver","MainActivity onStop()...")
}
}
class MainActivity : AppCompatActivity() {
private lateinit var observer: MyObserver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
observer = MyObserver()
}
override fun onStart() {
super.onStart()
observer.activityStart()
}
override fun onStop() {
super.onStop()
observer.activityStop()
}
}
这种方式虽然可行,但不够优雅,会在 Activity 中引入大量模版代码,使得与业务无关的逻辑耦合在了一起。
而 Lifecycles 组件就是为了解耦出现的。它可以让任何一个普通类,都能轻松、优雅地感知 Activity 的生命周期,而不需要在 Activity 编写任何额外的分发逻辑。
我们来改造一下,新建一个 MyObserver
类,并实现 DefaultLifecycleObserver
接口。我们可以重写一系列生命周期事件的方法:
kotlin
class MyObserver : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
// LifecycleOwner 表示拥有生命周期的对象
// 在 owner 的 onStart 回调之后调用
Log.d("MyObserver", "onStart")
}
override fun onStop(owner: LifecycleOwner) {
// 在 owner 的 onStop 回调之后调用
Log.d("MyObserver", "onStop")
}
}
那么,我们该怎么让 MyObserver
与拥有生命周期的对象(例如 Activity)关联起来呢?我们想要当 Activity
的生命周期发生变化的时候,MyObserver
能够自动接收到通知。要实现这个,我们只需借助 LifecycleOwner
接口即可。
我们可以通过 LifecycleOwner
实例的 lifecycle
属性来添加观察者:
kotlin
// 示范代码
lifecycleOwner.lifecycle.addObserver(MyObserver())
这里获取了 LifecycleOwner
实例的 Lifecycle
对象,并调用了该 Lifecycle
对象的 addObserver()
方法来注册我们的观察者。
我们的 Activity
默认继承自 AppCompatActivity
,而 AppCompatActivity
已经实现了 LifecycleOwner
接口。所以 MainActivity
本身就是一个 LifecycleOwner
实例,我们可以直接在 onCreate()
方法中添加观察者:
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
// 将观察者添加到生命周期中
lifecycle.addObserver(MyObserver())
}
}
当然 Fragment 也实现了
LifecycleOwner
接口,也是一个LifecycleOwner
实例。
可以看到,上述代码中没有任何的模版代码。并且 MyObserver
与 MainActivity
的生命周期解耦了,可以被复用到任何 LifecycleOwner
组件中。
现在,MyObserver
虽然能够被动感知 Activity
生命周期的变化,却不能主动获取当前生命周期的状态。为此,我们可以在 MyObserver
的构造函数中传入 Lifecycle
对象来实现:
kotlin
class MyObserver(private val lifecycle: Lifecycle) : DefaultLifecycleObserver {
fun doSomething() {
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// 只有当生命周期处于 STARTED 或 RESUMED 状态时才会执行
Log.d("MyObserver", "Component is active. Performing action.")
}
}
}
我们可以通过 lifecycle.currentState
来获取当前的生命周期状态,一共有 INITIALIZED
、DESTROYED
、CREATED
、STARTED
、RESUMED
这五种状态。它们与 Activity 生命周期回调的对应关系如下图所示:
图片源自 Google 官方文档
LiveData
学完了 Lifecycles
之后,我们再来看一个响应式编程组件 LiveData
。它可以包含任意类型的数据,并在数据发生变化时通知给观察者。绝大多数情况下,它都是配合 ViewModel
进行使用,成为 UI 与数据之间的桥梁。
LiveData 的基本用法
在 ViewModel
中,我们经常需要执行一些异步操作(如发起网络请求),当这些操作完成后才会更新数据。如果在 Activity
手动获取数据,很难保证获取的是异步操作完成后的新数据,导致界面中显示的是旧的状态。
所以,我们认为更理想的模式是:ViewModel
在数据变化后,应该主动通知 给 Activity
。而这一点,我们可以使用 LiveData
来完成。
修改 MainViewModel
,使用 LiveData
包装计数值:
kotlin
class MainViewModel(countReserved: Int) : ViewModel() {
// MutableLiveData 表示可变的 LiveData
private val _counter = MutableLiveData<Int>()
// 对外只暴露不可变的 LiveData,确保数据只能在 ViewModel 内部修改
val counter: LiveData<Int> = _counter
init {
// 恢复数据
_counter.value = countReserved
}
fun plusOne() {
val count = _counter.value ?: 0
_counter.value = count + 1
}
fun clear() {
_counter.value = 0
}
}
这里包含了一个很重要的最佳实践 :我们定义了一个私有的 MutableLiveData
(可变) 类型的 _counter
,对外暴露的则是一个 LiveData
(不可变) 类型的 counter
。这确保了 ViewModel
数据的封装性,在 Activity
或 Fragment
中只能观察数据,并不能修改数据,所有数据的修改逻辑都必须在 ViewModel
内部进行。
MutableLiveData
的常用方法:
-
getValue()
: 获取LiveData
中包含的数据,返回值可能为空。 -
setValue(T value)
: 设置LiveData
中的数据。只能在主线程中调用,在子线程中调用会直接抛出异常。 -
postValue(T value)
: 用于从子线程中安全地更新数据。它内部会通过 Handler 将更新任务分发给主线程执行,所以这个更新是异步的。
然后,我们在 Activity
中观察 LiveData
:
kotlin
class MainActivity : AppCompatActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// ...
binding.plusOneBtn.setOnClickListener {
viewModel.plusOne()
}
binding.clearBtn.setOnClickListener {
viewModel.clear()
}
// 调用 observe() 方法来观察数据的变化
viewModel.counter.observe(this) { count ->
// count 为最新的计数值
binding.infoText.text = "count: $count"
}
}
override fun onPause() {
super.onPause()
// 保存当前的数据
sp.edit {
putInt("count_reserved", viewModel.counter.value ?: 0)
}
}
}
运行程序,计数器还是可以正常工作。但现在我们无需手动从 ViewModel
中获取数据,LiveData
会在数据发生变化时自动将最新的数据推送给 Activity
。
看到这里,你可能会觉得 LiveData
好像和 Lifecycle
并没什么关系。其实不然,LiveData
之所以能够成为 Activity
与 ViewModel
之间安全、高效的通信桥梁,靠的就是 Lifecycles
组件感知生命周期的能力。
在我们在调用 observe()
方法时,它会通过传入的 LifecycleOwner
对象创建一个与 Activity
生命周期绑定的特殊观察者。
-
当
Activity
被销毁时,这个观察者会自动被移除,从而释放了对Activity
的引用,解决了内存泄露问题。 -
当
Activity
进入后台、对用户来说不可见时,LiveData
会停止推送数据更新,避免不必要的资源消耗;只有当Activity
处于前台时,LiveData
才会推送最新的数据,保证数据的一致性。
这一切,都是因为这个观察者监听了 Lifecycles
的状态变化。
map 和 switchMap
LiveData
还提供了 map()
和 switchMap()
这两种转换方法,来应对更复杂的需求场景。
map
方法用于转换 LiveData
的类型。假如我们有一个 User
对象,我们不想将整个 User
对象暴露给外部。这时,我们可以使用 map()
方法。
kotlin
data class User(var firstName: String, var lastName: String, var age: Int)
class MainViewModel(countReserved: Int) : ViewModel() {
private val userLiveData = MutableLiveData<User>()
// 只将表示用户姓名的 String 类型的 LiveData 暴露给外部
val userName: LiveData<String> = userLiveData.map { user ->
// 转换逻辑
"${user.firstName} ${user.lastName}"
}
}
这样,每当 userLiveData
的数据发生变化时,map
方法就会监听到,并执行其内部的转换逻辑,然后将转换后的新数据通知给 userName
的观察者。
switchMap()
可用于解决一个 LiveData
的产生依赖于另一个 LiveData
值的场景。例如,根据可变化的 userId
来获取对应的 User
对象。
首先,定义一个 Repository
,它的 getUser()
方法可以获取一个包含 User
数据的 LiveData
对象。
kotlin
object Repository {
fun getUser(userId: String): LiveData<User> {
val liveData = MutableLiveData<User>()
liveData.value = User(userId, userId, 0)
return liveData
}
}
注意:每次调用该方法都会创建一个新的 LiveData
实例。
如果我们直接观察该方法返回的 LiveData
实例,将无法在 userId
变化后,接收到新用户的更新。例如:
kotlin
// MainViewModel.kt
fun getUser(userId: String): LiveData<User> {
return Repository.getUser(userId)
}
// MainActivity.kt
viewModel.getUser(userId).observe(this){ user->
// ...
}
这时,我们可以使用 switchMap()
方法。在 MainViewModel
中添加如下内容:
kotlin
class MainViewModel(countReserved: Int) : ViewModel() {
// 用户 id
private val userIdLiveData = MutableLiveData<String>()
val user: LiveData<User> = userIdLiveData.switchMap { userId ->
Repository.getUser(userId)
}
fun searchUser(userId: String) {
// 现在只需更新用户 id 即可
userIdLiveData.value = userId
}
}
这样,每当 userIdLiveData
对象存放的用户 id 数据发生变化时,switchMap
就会执行,取消对旧 LiveData
实例的观察,并将观察的对象切换到 Repository.getUser()
方法返回的新的 LiveData
实例上。
我们来测试一下,在布局中新增一个按钮:
kotlin
<Button
android:id="@+id/searchUserBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Search User" />
在 MainActivity
的 onCreate()
方法中添加如下内容:
kotlin
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
// 观察固定的 LiveData 实例
viewModel.user.observe(this) { user ->
binding.infoText.text = user.firstName
}
binding.searchUserBtn.setOnClickListener {
val userId = (0..10000).random().toString()
viewModel.searchUser(userId) // 刷新用户 id 数据
}
}
...
}
运行程序,并点击 "Search User" 按钮。会发现界面中的数字一直在变化,说明我们在 MainActivity
成功观察到了数据的变化。以一种非常优雅的方式解决了动态数据源的观察问题。
最后,如果获取 LiveData
对象无需传入参数。我们只要创建一个空的 LiveData
对象,再在它的基础上调用 switchMap
方法即可。例如在下拉刷新中,用户下拉的动作就是一个触发信号。
kotlin
class MainViewModel(countReserved: Int) : ViewModel() {
// 空的 LiveData 对象
private val _refreshTrigger = MutableLiveData<Unit>()
val listData: LiveData<List<Item>> = _refreshTrigger.switchMap {
Repository.fetchNewList() // 假设该方法返回 LiveData<List<Item>> 实例
}
fun refresh() {
// 发送一个触发信号
_refreshTrigger.value = Unit
}
}
注意:我们通过设置 MutableLiveData
对象的数据来触发它的数据变化。因为 LiveData
内部并不会判断新旧数据是否相同,只要调用了 setValue()
或 postValue()
方法,就会触发数据变化事件。
StateFlow
现在,我们更多会使用 StateFlow
来代替 LiveData
。因为它天生拥抱协程,并解决了 LiveData
的历史遗漏问题(如 setValue
必须在主线程调用,缺少流操作符来处理复杂数据)。
我们来改造 MainViewModel
:
kotlin
class MainViewModel(countReserved: Int) : ViewModel() {
// StateFlow 对象必须要有初始值,避免了 LiveData 可能出现的空指针问题
private val _counter = MutableStateFlow(countReserved)
// 对外部暴露为不可变的 StateFlow
val counter: StateFlow<Int> = _counter.asStateFlow()
fun plusOne() {
// 使用 update 方法来更新数据,可以保证原子性操作
_counter.update { currentCount -> currentCount + 1 }
}
fun clear() {
_counter.value = 0
}
}
在 MainActivity
中,我们开启协程来收集 Flow
的数据。
kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.counter.collect { count ->
binding.infoText.text = "count: $count"
}
}
}
}
repeatOnLifecycle
方法能够保证在 Activity
生命周期状态至少为 STARTED
时,才会收集数据,也就是说 collect
代码块才会执行。当 Activity
进入 STOPPED
状态时,它会自动取消协程;当 Activity
重新回到 STARTED
状态时,它又会重新启动一个新的协程来收集数据。