Lifecycle
添加依赖
dependencies {
def lifecycle_version = "2.5.1"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// ViewModel utilities for Compose
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
}
Lifecycle
使用两种主要枚举跟踪其关联组件的生命周期状态:
- 事件:从框架和 Lifecycle 类分派的生命周期事件。这些事件映射到
activity
和fragment
中的回调事件。 - 状态:由 Lifecycle 对象跟踪的组件的当前状态。
可以将状态看作图中的节点,将事件看作这些节点之间的边。
实现 LifecycleObserver 观察者
实现 LifecycleObserver
观察者,有两种方法,一种是实现 LifecycleEventObserver
接口,覆写 onStateChanged()
方法,在该方法中监听不同的生命周期事件:
import android.util.Log
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Lifecycle.Event
class MyLifecycleObserver: LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Event) {
Log.e(TAG, "onStateChanged 事件来源: ${source.javaClass.name}")
when (event) {
Event.ON_CREATE -> {
Log.e(TAG, "onStateChanged: ON_CREATE")
}
Event.ON_START -> {
Log.e(TAG, "onStateChanged: ON_START")
}
Event.ON_RESUME -> {
Log.e(TAG, "onStateChanged: ON_RESUME")
}
Event.ON_PAUSE -> {
Log.e(TAG, "onStateChanged: ON_PAUSE")
}
Event.ON_STOP -> {
Log.e(TAG, "onStateChanged: ON_STOP")
}
Event.ON_DESTROY -> {
Log.e(TAG, "onStateChanged: ON_DESTROY")
}
Event.ON_ANY -> {
Log.e(TAG, "onStateChanged: ON_ANY")
}
else -> {}
}
}
companion object {
private val TAG = MyLifecycleObserver::class.java.simpleName
}
}
另一种方法是实现 DefaultLifecycleObserver
接口,该接口是支持默认方法实现的接口类(java8),然后选择你要监听的生命周期方法进行覆写即可:
import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
class MyLifecycleObserver2 : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
Log.e(TAG, "onCreate: ")
}
override fun onStart(owner: LifecycleOwner) {
Log.e(TAG, "onStart: ")
}
override fun onResume(owner: LifecycleOwner) {
Log.e(TAG, "onResume: ")
}
override fun onPause(owner: LifecycleOwner) {
Log.e(TAG, "onPause: ")
}
override fun onStop(owner: LifecycleOwner) {
Log.e(TAG, "onStop: ")
}
override fun onDestroy(owner: LifecycleOwner) {
Log.e(TAG, "onDestroy: ")
}
companion object {
private val TAG = MyLifecycleObserver2::class.java.simpleName
}
}
实现 LifecycleObserver
观察者对象之后,通过调用 Lifecycle
类的 addObserver()
方法来添加观察者对象:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(MyLifecycleObserver())
lifecycle.addObserver(MyLifecycleObserver2())
}
}
添加观察者对象通常是在 Activity
或者 Fragment
中进行,因为这二者都是LifecycleOwner
接口实现者,它们天生自带 Lifecycle
实例。
LifecycleOwner
LifecycleOwner
是单一方法接口,表示类具有 Lifecycle
。它具有一种方法(即 getLifecycle()
),该方法必须由类实现。 此接口从各个类(如 Fragment
和 AppCompatActivity
)抽象化 Lifecycle
的所有权,并允许编写与这些类搭配使用的组件。任何自定义应用类均可实现 LifecycleOwner
接口。
实现 DefaultLifecycleObserver
的组件可与实现 LifecycleOwner
的组件完美配合,因为所有者可以提供生命周期,而观察者可以注册以观察生命周期。
对于位置跟踪示例,我们可以让 MyLocationListener
类实现 DefaultLifecycleObserver
,然后在 onCreate()
方法中使用 activity
的 Lifecycle
对其进行初始化。这样,MyLocationListener
类便可以“自给自足”,这意味着,对生命周期状态的变化做出响应的逻辑会在 MyLocationListener
(而不是在 activity
)中进行声明。让各个组件存储自己的逻辑可使 activity
和 fragment
逻辑更易于管理。
internal class MyLocationListener(
private val context: Context,
private val lifecycle: Lifecycle,
private val callback: (Location) -> Unit
): DefaultLifecycleObserver {
private var enabled = false
override fun onStart(owner: LifecycleOwner) {
if (enabled) {
// connect
}
}
fun enable() {
enabled = true
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// connect if not connected
}
}
override fun onStop(owner: LifecycleOwner) {
// disconnect if connected
}
}
对于此实现,LocationListener 类可以完全感知生命周期。如果我们需要从另一个 activity 或 fragment 使用 LocationListener,只需对其进行初始化。所有设置和拆解操作都由类本身管理。
class MyActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener
override fun onCreate(...) {
myLocationListener = MyLocationListener(this, lifecycle) { location ->
// update UI
}
Util.checkUserStatus { result ->
if (result) {
myLocationListener.enable()
}
}
}
}
实现自定义 LifecycleOwner
支持库 26.1.0
及更高版本中的 Fragment
和 Activity
均已实现 LifecycleOwner
接口。
如果你有一个自定义类并希望使其成为 LifecycleOwner
,可以使用 LifecycleRegistry
类,但需要将事件转发到该类,如以下代码示例中所示:
class MyActivity : Activity(), LifecycleOwner {
private lateinit var lifecycleRegistry: LifecycleRegistry
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleRegistry = LifecycleRegistry(this)
lifecycleRegistry.markState(Lifecycle.State.CREATED)
}
public override fun onStart() {
super.onStart()
lifecycleRegistry.markState(Lifecycle.State.STARTED)
}
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
}
Lifecycle的实现原理
请参考我的另一篇博文:【深入理解Kotlin协程】lifecycleScope源码追踪扒皮 ,其中对Lifecycle的源码流程进行了简要分析。
简要总结:
Activity
的getLifecycle
方法返回的是一个LifecycleRegistry
对象,LifecycleRegistry
类正是Lifecycle
接口的实现者。LifecycleRegistry
通过弱引用持有了LifecycleOwner
的对象,也就是Activity
对象 。- 在
Activity
的onCreate()
方法中添加了一个透明的ReportFragment
来专门处理生命周期。 - 在
ReportFragment
中,API >= 29
时的处理逻辑是,调用LifecycleCallbacks.registerIn(activity)
方法, 其内容是:为Activity
注册一个Application.ActivityLifecycleCallbacks
的回调接口实现类,这个Callback
被Actvity
内部保存了起来。在 ATMS 跨进程调用ActivityThread
中的handleStartActivity
方法时,回调Activity
的performStart()
方法,进而回调其保存的Callback
的回调方法,在其中拿到Activity持有的LifecycleRegistry
对象进行分发处理,最终调用到LifecycleEventObserver
观察者对象的onStateChanged()
接口方法。 - 在
ReportFragment
中,API < 29
时的处理逻辑是, 在ReportFragment
的生命周期方法里,进行事件分发,进而回调LifecycleEventObserver
的onStateChanged()
接口方法。 - 普通的
Fragment
中生命周期的处理流程类似,也是交给LifecycleRegistry
来分发,但是生命周期方法的触发是通过宿主Activity
实现的。
监听 Application 的前后台切换
监听 Application 的前后台切换在以前是通过 registerActivityLifecycleCallbacks()
判断当前处于栈顶的 Activity 的状态来判断的,现在Jetpack提供了一个简单的API ProcessLifecycleOwner
来直接注册监听:
class MyApp: Application() {
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(AppObserver())
}
class AppObserver : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
Analytics.report("MyApp --> onCreate")
}
override fun onStart(owner: LifecycleOwner) {
Analytics.report("MyApp --> onStart")
}
override fun onResume(owner: LifecycleOwner) {
Analytics.report("MyApp --> onResume")
}
override fun onPause(owner: LifecycleOwner) {
Analytics.report("MyApp --> onPause")
}
override fun onStop(owner: LifecycleOwner) {
Analytics.report("MyApp --> onStop")
}
override fun onDestroy(owner: LifecycleOwner) {
Analytics.report("MyApp --> onDestroy")
}
}
object Analytics {
fun report(state: String) = Log.e("Analytics", state)
}
}
这里的Observer是复用了Activity的LifecycleObserver,并没有提供一个专门为Application使用的Observer,不过不影响我们监听Application的前后台切换。
当应用启动时会依次调用:
- MyApp --> onCreate
- MyApp --> onStart
- MyApp --> onResume
当应用按Home键返回桌面或者切换到最近应用列表界面时会依次调用:
- MyApp --> onPause
- MyApp --> onStop
当从界面返回到应用时会依次调用:
- MyApp --> onStart
- MyApp --> onResume
可以看到只有应用第一次创建时会回调 onCreate
方法,后面应用前后台切换时会在 onPause/onStop
和 onStart/onResume
两组回调方法之间切换,可以根据需要选择对应的回调方法进行监听业务处理。
LiveData
LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 activity、fragment 或 service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
如果观察者(由 Observer 类表示)的生命周期处于 STARTED
或 RESUMED
状态,则 LiveData 会认为该观察者处于活跃状态。LiveData 只会将更新通知给活跃的观察者。为观察 LiveData 对象而注册的非活跃观察者不会收到更改通知。 (这一点非常重要,也就是说在页面不可见时,观察者对象中的处理逻辑不会被执行,从而节约宝贵的内存和CPU资源。)
使用 LiveData 具有以下优势:
- 确保界面符合数据状态: LiveData 遵循观察者模式。当底层数据发生变化时,LiveData 会通知 Observer 对象。你可以整合代码以在这些 Observer 对象中更新界面。这样一来,您无需在每次应用数据发生变化时更新界面,因为观察者会替您完成更新。
- 不会发生内存泄漏: 观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。
- 不会因 Activity 停止而导致崩溃: 如果观察者的生命周期处于非活跃状态(如返回堆栈中的 activity),它便不会接收任何 LiveData 事件。
- 不再需要手动处理生命周期: 界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。
- 数据始终保持最新状态: 如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
- 适当的配置更改: 如果由于配置更改(如设备旋转)而重新创建了 activity 或 fragment,它会立即接收最新的可用数据。
- 共享资源 你可以使用单例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象。
创建 LiveData
LiveData 对象通常存储在 ViewModel 对象中,并可通过 getter
方法进行访问,如以下示例中所示:
class MyViewModel : ViewModel() {
private val _currentName: MutableLiveData<String> by lazy { MutableLiveData<String>("Tom") }
val currentName: LiveData<String> = _currentName
fun updateName(name: String) {
_currentName.value = name // 只能在主线程调用
// _currentName.postValue(name) // 主线程、子线程中都可以调用
}
}
LiveData 没有公开可用的方法来更新存储的数据。MutableLiveData 类公开 setValue(T)
和 postValue(T)
方法来修改存储在 LiveData 对象中的值。通常情况下会在 ViewModel 中使用 MutableLiveData,然后 ViewModel 只会向观察者公开不可变的 LiveData 对象。
setValue(T)
和 postValue(T)
方法的区别:
- liveData.postValue():可以在任意的线程下执行, 内部会post到主线程调用setValue
- liveData.setValue():只能在主线程中执行
LiveData 的 postValue()
方法有一定的延时,如果想在 postValue()
之后移除观察者,马上调用移除方法,可能会导致部分观察者收不到前面postValue的数据,此时应该使用LiveData的setValue()
,它是即时的进行分发,在下一行代码进行移除观察者也没问题。
注意:请确保用于更新界面的 LiveData 对象存储在 ViewModel 对象中,而不是将其存储在 activity 或 fragment 中,原因如下:
- 避免 Activity 和 Fragment 过于庞大。现在,这些界面控制器负责显示数据,但不负责存储数据状态。
- 将 LiveData 实例与特定的 Activity 或 Fragment 实例分离开,并使 LiveData 对象在配置更改后继续存在。
观察 LiveData 对象
在大多数情况下,应用组件的 onCreate()
方法是开始观察 LiveData 对象的正确着手点,原因如下:
- 确保系统不会从 Activity 或 Fragment 的
onResume()
方法进行冗余调用。 - 确保 activity 或 fragment 变为活跃状态后具有可以立即显示的数据。一旦应用组件处于
STARTED
状态,就会从它正在观察的 LiveData 对象接收最新值。只有在设置了要观察的 LiveData 对象时,才会发生这种情况。
通常,LiveData 仅在数据发生更改时才发送更新,并且仅发送给活跃观察者。此行为的一种例外情况是,观察者从非活跃状态更改为活跃状态时也会收到更新。此外,如果观察者第二次从非活跃状态更改为活跃状态,则只有在自上次变为活跃状态以来值发生了更改时,它才会收到更新。
以下示例代码说明了如何开始观察 LiveData 对象:
class MyActivity: ComponentActivity() {
private val myViewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.livedata_test)
val nameTextView = findViewById<TextView>(R.id.tv_user_name)
val updateBtn = findViewById<Button>(R.id.btn_update)
myViewModel.currentName.observe(this) { newName ->
nameTextView.text = newName
}
updateBtn.setOnClickListener {
myViewModel.updateName("张三")
}
}
}
该示例中演示的是按下按钮调用的 setValue()
方法,但也可以出于各种各样的原因调用 setValue()
或 postValue()
来更新 name,这些原因可能包括响应网络请求或数据库加载完成等等。在所有情况下,调用 setValue()
或 postValue()
都会触发观察者并更新界面。
注意:你必须保证调用 setValue(T)
方法时是从主线程更新 LiveData 对象。如果在子线程中执行代码,可以改用 postValue(T) 方法来更新 LiveData 对象。
应用架构中的 LiveData
LiveData 具有生命周期感知能力,遵循 activity 和 fragment 等实体的生命周期。你可以使用 LiveData 在这些生命周期所有者和生命周期不同的其他对象(例如 ViewModel 对象)之间传递数据。ViewModel 的主要责任是加载和管理与界面相关的数据,因此非常适合作为用于保留 LiveData 对象的备选方法。你可以在 ViewModel 中创建 LiveData 对象,然后使用这些对象向界面层公开状态。
activity 和 fragment 不应保留 LiveData 实例,因为它们的用途是显示数据,而不是保持状态。此外,如果 activity 和 fragment 无需保留数据,还可以简化单元测试的编写。
你可能会想在数据层类中使用 LiveData 对象,但 LiveData 并不适合用于处理异步数据流。如果您需要在应用的其他层中使用数据流,请考虑使用 Kotlin Flow,然后使用 asLiveData()
在 ViewModel 中将 Kotlin Flow 转换成 LiveData。如需详细了解如何搭配使用 Kotlin Flow 与 LiveData,请学习此 Codelab。
扩展 LiveData
如果观察者的生命周期处于 STARTED
或 RESUMED
状态,LiveData 会认为该观察者处于活跃状态。以下示例代码说明了如何扩展 LiveData 类:
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() {
stockManager.removeUpdates(listener)
}
}
本示例中的价格监听器实现包括以下重要方法:
- 当 LiveData 对象具有活跃观察者时,会调用
onActive()
方法。这意味着,你需要从此方法开始观察股价更新。 - 当 LiveData 对象没有任何活跃观察者时,会调用
onInactive()
方法。由于没有观察者在监听,因此没有理由与 StockManager 服务保持连接。
setValue(T)
方法将更新 LiveData 实例的值,并将更改告知活跃观察者。
使用 StockLiveData 类,如下所示:
public class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val myPriceListener: LiveData<BigDecimal> = StockLiveData("xxxx")
myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
// Update the UI.
})
}
}
observe()
方法的第一个参数owner
将通过 Fragment
的 getViewLifecycleOwner()
方法来获取 LifecycleOwner
对象进行传递。这样做表示此观察者已绑定到与所有者关联的 Lifecycle
对象,这意味着:
- 如果 Lifecycle 对象未处于活跃状态,那么即使值发生更改,也不会调用观察者。
- 销毁 Lifecycle 对象后,会自动移除观察者。
LiveData 对象具有生命周期感知能力,这一事实意味着你可以在多个 Activity、Fragment 和 Service 之间共享这些对象。为使示例保持简单,你可以将 LiveData 类实现为一个单例,如下所示:
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager: StockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() {
stockManager.removeUpdates(listener)
}
companion object {
private lateinit var sInstance: StockLiveData
@MainThread
fun get(symbol: String): StockLiveData {
sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
return sInstance
}
}
}
在 Fragment 中使用它,如下所示:
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
// Update the UI.
})
}
多个 Fragment 和 Activity 可以观察 MyPriceListener 实例。仅当一个或多项系统服务可见且处于活跃状态时,LiveData 才会连接到该服务。
转换 LiveData
当需要根据另一个LiveData实例的值返回不同的 LiveData 实例时,可以通过 Transformations.map()
或者 Transformations.switchMap()
方法来实现:
val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData) {
user -> "${user.name} ${user.lastName}"
}
private fun getUser(id: String): LiveData<User> {
...
}
val userId: LiveData<String> = ...
val user = Transformations.switchMap(userId) { id -> getUser(id) }
转换是以延迟的方式计算,所以与生命周期相关的行为会隐式传递下去,而不需要额外的显式调用或依赖项。例如,假设您有一个界面组件,该组件接受地址并返回该地址的邮政编码。您可以将邮政编码查询实现为地址输入的转换,如以下示例中所示:
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
private val addressInput = MutableLiveData<String>()
private fun setInput(address: String) {
addressInput.value = address
}
val postalCode: LiveData<String> = Transformations.switchMap(addressInput) {
address -> repository.getPostCode(address)
}
}
在这种情况下,postalCode
字段定义为 addressInput
的转换。只要您的应用具有与 postalCode
字段关联的活跃观察者,就会在每次 addressInput
发生更改时重新计算并检索该字段的值。
合并多个 LiveData 源
使用 MediatorLiveData
对象可以合并多个 LiveData 源。
考虑以下场景:我们有 2 个实例LiveData
,它们分别为liveData1
和liveData2
,我们希望将它们观察的数据合并到一个 MediatorLiveData
类型的 liveDataMerger
对象中,然后liveData1
和liveData2
将成为 liveDataMerger
的数据来源,每次当它们二者中的任何一个数据发生变化时,我们都会在 onChanged
方法中将新值设置给 liveDataMerger
,以便由liveDataMerger
的观察者统一处理。那么可以这样做:
val liveData1 = MutableLiveData<String>()
val liveData2 = MutableLiveData<String>()
val liveDataMerger = MediatorLiveData<String>().apply {
addSource(liveData1) { value1 -> value = value1 }
addSource(liveData2) { value2 -> value = value2 }
}
// 一旦 liveData1 或者 liveData2 数据发生了改变,observer就能观察到,以便统一更新UI界面
liveDataMerger.observe(owner) { value ->
// 更新UI
}
MediatorLiveData
还可以做更加精细的控制,例如:假设我们只想将 liveData1
发送的 10
个值合并到liveDataMerger
中,然后,在 10
个值之后,我们就停止监听 liveData1
并将其从监听源中删除。那么可以这样做:
liveDataMerger.addSource(liveData1, object : Observer<String> {
var count = 0
override fun onChanged(value: String) {
if (++count > 10) {
liveDataMerger.removeSource(liveData1)
} else {
liveDataMerger.value = value
}
}
})
LiveData 原理分析
LiveData的observe()
方法源码:
在 observe()
方法内,真正的 observer
被包装成了一个LifecycleBoundObserver
对象, 并保存在 mObservers
对象中(一个Map对象),然后使用传入的owner
获取Lifecycle
对象进行注册。
传入 LifecycleBoundObserver
的observer
被保存在它的父类ObserverWrapper
中,其父类是一个抽象类:
在父类ObserverWrapper
中定义了一个mLastVersion
版本号,它的初始值是-1
。
并且在 LifecycleBoundObserver
当中会判断当前应用组件的生命周期状态是 DESTROYED
时会移除持有的Observer
对象:
再来看 setValue()
的执行逻辑:
这里mVersion
的初始值也是-1
,每执行一次,mVersion
的值便会+1
。
setValue()
调用了 dispatchingValue()
方法:
这里会遍历mObservers
这个Map
对象中保存的每个Observer
对象并传入到 considerNotify
方法中:
在 considerNotify
方法中处理比较简单,就是判断了一下版本号,然后调用传入的observer
拿到真正的observer
对象的 onChanged
方法进行回调。
再看 postValue()
方法:
postValue()
方法就是简单的将一个Runnable对象post到主线程中去执行,然后在这个Runnable对象中调用了setValue()
方法。
总结:
LiveData
之所以能够感知到组件的生命周期,是因为在LiveData
里面给组件中注册了一个生命周期的观察者LifeCycleObserver
。- 然后将
Activity
本身作为LifecycleOwner
被观察者注册观察者:owner.getLifecycle().addObserver(wrapperObserver)
(Activity
实现了LifecycleOwner
接口) Activity
销毁时,LiveData
注册的Lifecycle
监听onStateChanged
方法中会将对应的观察者移除,不会内存泄漏。liveData.postValue()
可以跨Activity页面跨组件发送数据,只要接受的页面注册了观察者接口就能收到数据
解决 LiveData 的粘性事件
LiveData 的粘性事件是指后注册的观察者也能收到之前发送的数据,跟EventBus的粘性事件一样。但是LiveData 没有提供粘性事件行为控制的开关,默认就是自带粘性事件功能的。
通过前面源码分析得知,造成粘性事件主要的关键是在于观察者的版本号mLastVersion
与当前LiveData对象的版本号mVersion
的比较:
前面分析可知,父类ObserverWrapper
中定义了的mLastVersion
版本号,它和mVersion
的初始值都是-1
。
而由于 LiveData
先发送数据时,调用setValue
会导致当前 LiveData
的 mVersion
的值从初始值-1
变成0
,而后注册的Observer
监听对象的mLastVersion
的值还是初始值-1
,因此走到这里时,判断条件observer.mLastVersion >= mVersion
不满足,会继续往下走,执行进行回调。并且会将Observer
监听对象的mLastVersion
与 mVersion
进行对齐,表示已经分发过了,在下次再次进入considerNotify
方法时,就会走return
逻辑。
因此解决方法就是在LiveData
注册观察者Observer
时,将Observer
的 mLastVersion
版本号修改为大于等于LiveData
的 mVersion
版本号。
可以继承MutableLiveData
覆写observe
方法,通过反射手段来修改:
class CustomLiveData<T> : MutableLiveData<T>() {
var stick: Boolean = false
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, observer)
if (!stick) {
hook(observer)
}
}
/**
* 反射修改 observer 的 mLastVersion 的值为 mVersion
*/
fun hook(observer: Observer<in T>) {
val liveDataClass = LiveData::class.java
val mObserversField = liveDataClass.getDeclaredField("mObservers")
mObserversField.isAccessible = true
val mObservers = mObserversField.get(this)
val observerClass = mObservers::class.java
val get = observerClass.getDeclaredMethod("get", Any::class.java)
get.isAccessible = true
val entry = get.invoke(mObservers, observer)
val observerWrapper = (entry as Map.Entry<Any, Any>).value
val wrapperClass = observerWrapper.javaClass.superclass
val mLastVersion = wrapperClass.getDeclaredField("mLastVersion")
mLastVersion.isAccessible = true
val mVersion = liveDataClass.getDeclaredField("mVersion")
mVersion.isAccessible = true
val mVersionValue = mVersion.get(this)
mLastVersion.set(observerWrapper, mVersionValue)
}
}
定义一个 LiveDataBus
单例模式作为管理工具类来使用:
class LiveDataBus {
companion object {
private val liveData: CustomLiveData<Any> by lazy { CustomLiveData() }
fun stick(isStick: Boolean = true): Companion {
liveData.stick = isStick
return this
}
fun<T> postValue(value: T) {
liveData.postValue(value)
}
fun<T : Any> observe(owner: LifecycleOwner, observer: Observer<in T>) {
liveData.observe(owner, observer as Observer<in Any>)
}
}
}
使用示例:
class MyActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.livedata_test)
val nameTextView = findViewById<TextView>(R.id.tv_user_name)
val gotoOtherBtn = findViewById<Button>(R.id.btn_goto_other)
gotoOtherBtn.setOnClickListener {
// LiveDataBus.stick().postValue("李四") // 粘性发送
LiveDataBus.postValue("李四") // 默认非粘性发送
startActivity(Intent(this, OtherLiveDataActivity::class.java))
}
LiveDataBus.observe<String>(this) { newName ->
nameTextView.text = newName
}
}
}
class OtherLiveDataActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.livedata_other)
val nameTextView = findViewById<TextView>(R.id.tv_user_name)
LiveDataBus.observe<String>(this) { newName ->
nameTextView.text = newName
}
}
}
ViewModel
ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于封装相关的业务逻辑并将状态公开给界面。 它的主要优点是,它可以缓存数据并在配置更改导致页面销毁重建后持久保留相应状态。 (配置变更包括:屏幕旋转、字体调整、分辨率调整、权限变更等等)
持久性
ViewModel 允许数据在 ViewModel 持有的状态和 ViewModel 触发的操作结束后继续存在。这种缓存意味着在常见的配置更改(例如屏幕旋转)完成后,您无需重新提取数据。
作用域
实例化 ViewModel
时,您会向其传递实现 ViewModelStoreOwner
接口的对象。它可能是 Navigation Destination、Navigation Graph、activity、fragment
或实现接口的任何其他类型。 ViewModel
的作用域将限定为 ViewModelStoreOwner
的 Lifecycle
。它会一直保留在内存中,直到其 ViewModelStoreOwner
永久消失。
ViewModelStoreOwner
接口的直接子类为 ComponentActivity
、Fragment
和 NavBackStackEntry
。如需查看ViewModelStoreOwner
接口的间接子类的完整列表,请参阅 ViewModelStoreOwner 参考文档。
当 ViewModel
的作用域 fragment
或 activity
被销毁时,异步工作会在作用域限定到该 fragment
或 activity
的 ViewModel
中继续进行,这是持久性的关键。
对业务逻辑的访问权限
尽管绝大多数业务逻辑都存在于数据层中,但界面层也可以包含业务逻辑。ViewModel 是在界面层处理业务逻辑的正确位置。当需要应用业务逻辑来修改应用数据时,ViewModel 还负责处理事件并将其委托给层次结构中的其他层。
实现 ViewModel
以下是 ViewModel 的一个实现示例:该用例是显示用户列表。在此示例中,获取和保存用户列表的责任在于 ViewModel,而不直接在于 activity 或 fragment。
data class DiceUiState(
val firstDieValue: Int? = null,
val secondDieValue: Int? = null,
val numberOfRolls: Int = 0,
)
class DiceRollViewModel : ViewModel() {
// Expose screen UI state
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// Handle business logic
fun rollDice() {
_uiState.update { currentState ->
currentState.copy(
firstDieValue = Random.nextInt(from = 1, until = 7),
secondDieValue = Random.nextInt(from = 1, until = 7),
numberOfRolls = currentState.numberOfRolls + 1,
)
}
}
}
然后,你可以从 Activity 访问该列表,如下所示:
import androidx.activity.viewModels
class DiceRollActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same DiceRollViewModel instance created by the first activity.
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
val viewModel: DiceRollViewModel by viewModels()
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
// Update UI elements
}
}
}
}
}
Jetpack Compose 中的使用方式:
import androidx.lifecycle.viewmodel.compose.viewModel
// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
viewModel: DiceRollViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// Update UI elements
}
注意:
ViewModel
通常不应引用View
、Lifecycle
或可能存储对activity
上下文的引用的任何类。由于ViewModel
的生命周期大于界面的生命周期,因此在ViewModel
中保留与生命周期相关的 API 可能会导致内存泄漏。
创建 ViewModel 对象
有两种方式:
- 一种是通过
by
委托语法交给viewMoels()
扩展函数来创建 - 一种是通过
ViewModelProvider
来创建
val myViewModel: MyViewModel by viewModels()
val myViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
ViewModel 的生命周期
ViewModel
的生命周期与其作用域直接关联。ViewModel
会一直保留在内存中,直到其作用域 ViewModelStoreOwner
消失。以下上下文中可能会发生这种情况:
- 对于
activity
,是在activity
完成时。 - 对于
fragment
,是在fragment
分离时。 - 对于
Navigation
条目,是在Navigation
条目从返回堆栈中移除时。
这使得 ViewModels 成为了存储在配置更改后仍然存在的数据的绝佳解决方案。
上图说明了 activity
经历屏幕旋转而后结束时所处的各种生命周期状态。该图还在关联的 activity
生命周期的旁边显示了 ViewModel
的生命周期。此图表示的 activity
的各种基本状态同样适用于 fragment
的生命周期。
通常在系统首次调用 activity
对象的 onCreate()
方法时请求 ViewModel
。系统可能会在 activity
的整个生命周期内多次调用 onCreate()
,如在旋转设备屏幕时。ViewModel
存在的时间范围是从您首次请求 ViewModel
直到 activity
完成并销毁。
清除 ViewModel 依赖项
当 ViewModelStoreOwner
在 ViewModel
的生命周期内销毁 ViewModel
时,ViewModel
会调用 onCleared
方法。这样,您就可以清理遵循 ViewModel
生命周期的任何工作或依赖项。
以下示例展示了 viewModelScope
的替代方法。 viewModelScope
是一个内置 CoroutineScope
,会自动遵循 ViewModel
的生命周期。ViewModel
使用 viewModelScope
触发与业务相关的操作。如果您想使用自定义作用域(而不是 viewModelScope
)使测试更简单,ViewModel
可以在其构造函数中接收 CoroutineScope
作为依赖项。如果 ViewModelStoreOwner
在 ViewModel
的生命周期结束时清除 ViewModel
,ViewModel
也会取消 CoroutineScope
。
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
// Other ViewModel logic ...
override fun onCleared() {
coroutineScope.cancel()
}
}
从 Lifecycle
版本 2.5
及更高版本开始,您可以将一个或多个 Closeable
对象传递给ViewModel
的构造函数,以便在清除 ViewModel
实例时自动关闭的 ViewModel
。
class CloseableCoroutineScope(
context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
class MyViewModel(
private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
// Other ViewModel logic ...
}
ViewModel 的作用域 API
作用域是有效使用 ViewModel
的关键。每个 ViewModel
的作用域都限定为一个实现 ViewModelStoreOwner
接口的对象。借助 ViewModelProvider.get()
方法,可以获取作用域限定为任何 ViewModelStoreOwner
的 ViewModel
实例。
将 ViewModel 的作用域限定为最近的 ViewModelStoreOwner
要将 ViewModel
的作用域限定为最近的 ViewModelStoreOwner
非常简单,只需要借助 Activity
、Fragment
和 Navigation
提供的 viewModels()
扩展函数,以及 Compose
中的 viewModel()
函数来创建ViewModel
实例即可。
import androidx.activity.viewModels
class MyActivity : AppCompatActivity() {
// ViewModel API available in activity.activity-ktx
// The ViewModel is scoped to `this` Activity
val viewModel: MyViewModel by viewModels()
}
import androidx.fragment.app.viewModels
class MyFragment : Fragment() {
// ViewModel API available in fragment.fragment-ktx
// The ViewModel is scoped to `this` Fragment
val viewModel: MyViewModel by viewModels()
}
Jetpack Compose 中的使用方式:
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
modifier: Modifier = Modifier,
// ViewModel API available in lifecycle.lifecycle-viewmodel-compose
// The ViewModel is scoped to the closest ViewModelStoreOwner provided
// via the LocalViewModelStoreOwner CompositionLocal. In order of proximity,
// this could be the destination of a Navigation graph, the host Fragment,
// or the host Activity.
viewModel: MyViewModel = viewModel()
) { /* ... */ }
注意:如果您在
Jetpack Compose
中使用的是Hilt
来依赖注入ViewModel,请将viewModel()
调用替换为hiltViewModel()
将 ViewModel 的作用域限定为任何 ViewModelStoreOwner
View
系统中的 ComponentActivity.viewModels()
函数和 Fragment.viewModels()
函数以及 Compose
中的 viewModel()
函数接受可选的 ownerProducer
参数,可用于指定 ViewModel
实例的作用域限定为哪个 ViewModelStoreOwner
。以下示例展示了如何获取作用域限定为父 fragment
的 ViewModel
实例:
import androidx.fragment.app.viewModels
class MyFragment : Fragment() {
// ViewModel API available in fragment.fragment-ktx
// The ViewModel is scoped to the parent of `this` Fragment
val viewModel: SharedViewModel by viewModels(
ownerProducer = { requireParentFragment() }
)
}
Jetpack Compose 中的使用方式:
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
context: Context = LocalContext.current,
// ViewModel API available in lifecycle.lifecycle-viewmodel-compose
// The ViewModel is scoped to the parent of the host Fragment
// where this composable function is called
viewModel: MyViewModel = viewModel(
viewModelStoreOwner = (context as FragmentActivity)
)
) { /* ... */ }
从 fragment
获取作用域限定为 activity
的 ViewModel
是一种常见用例。为此,可以使用 activityViewModels()
扩展函数:
import androidx.fragment.app.activityViewModels
class MyFragment : Fragment() {
// ViewModel API available in fragment.fragment-ktx
// The ViewModel is scoped to the host Activity
val viewModel: SharedViewModel by activityViewModels()
}
将 ViewModel 的作用域限定为 Navigation 导航图
Navigation
导航图 也是 ViewModelStoreOwner
。如果您使用的是 Navigation Fragment
或 Navigation Compose
,可以使用 navGraphViewModels(graphId)
扩展函数获取作用域限定为某个 Navigation
导航图 的 ViewModel
实例。
import androidx.navigation.navGraphViewModels
class MyFragment : Fragment() {
// ViewModel API available in navigation.navigation-fragment
// The ViewModel is scoped to the `nav_graph` Navigation graph
val viewModel: SharedViewModel by navGraphViewModels(R.id.nav_graph)
// Equivalent navGraphViewModels code using the viewModels API
val viewModel: SharedViewModel by viewModels(
{ findNavController().getBackStackEntry(R.id.nav_graph) }
)
}
Jetpack Compose 中的使用方式:
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyAppNavHost() {
// ...
composable("myScreen") { backStackEntry ->
// Retrieve the NavBackStackEntry of "parentNavigationRoute"
val parentEntry = remember(backStackEntry) {
navController.getBackStackEntry("parentNavigationRoute")
}
// Get the ViewModel scoped to the `parentNavigationRoute` Nav graph
val parentViewModel: SharedViewModel = viewModel(parentEntry)
// ...
}
}
如果除了 Jetpack Navigation
之外,还可以使用 Hilt
的 hiltNavGraphViewModels(graphId)
API,如下所示:
import androidx.hilt.navigation.fragment.hiltNavGraphViewModels
class MyFragment : Fragment() {
// ViewModel API available in hilt.hilt-navigation-fragment
// The ViewModel is scoped to the `nav_graph` Navigation graph
// and is provided using the Hilt-generated ViewModel factory
val viewModel: SharedViewModel by hiltNavGraphViewModels(R.id.nav_graph)
}
Jetpack Compose 中的使用方式:
import androidx.hilt.navigation.compose.hiltViewModel
@Composable
fun MyAppNavHost() {
// ...
composable("myScreen") { backStackEntry ->
val parentEntry = remember(backStackEntry) {
navController.getBackStackEntry("parentNavigationRoute")
}
// ViewModel API available in hilt.hilt-navigation-compose
// The ViewModel is scoped to the `parentNavigationRoute` Navigation graph
// and is provided using the Hilt-generated ViewModel factory
val parentViewModel: SharedViewModel = hiltViewModel(parentEntry)
// ...
}
}
创建作用域跨页面的 ViewModel
可以让 Application
实现 ViewModelStoreOwner
接口
class MyApp: Application(), ViewModelStoreOwner {
val appViewModelStore by lazy { ViewModelStore() }
override fun getViewModelStore(): ViewModelStore {
return appViewModelStore
}
}
在 Activity
中可以使用ViewModelProvider(owner).get()
方法将 Application
作为owner
参数传入即可:
class MyActivity: ComponentActivity() {
val viewModel = ViewModelProvider(application as MyApp).get(MyViewModel::class.java)
......
}
在 Fragment
等其他组件中可以使用前面提过的限定作用域 API 来设置将 Application
作为 ViewModelStoreOwner
对象来提供:
import androidx.fragment.app.viewModels
class MyFragment : Fragment() {
val viewModel: MyViewModel by viewModels(
ownerProducer = { context as MyApp }
)
}
进阶用法:使用 SavedStateHandle 在进程重建时保留数据
借助 SaveStateHandle
,不仅可以在更改配置后持久保留数据,还可以在进程重新创建过程中持久保留数据。也就是说,即使用户关闭应用,稍后又将其打开,您的界面状态也可以保持不变。
在这种情况下,无论是配置变更、还是因为内存不足、电量不足等原因被系统杀死重建应用后,之前存储的数据都可以被复用,即便获取到的ViewModel不是同一个实例。
从 Fragment 1.2.0
或其传递依赖项 Activity 1.1.0
开始, ViewModel
对象可以接受 SavedStateHandle
作为构造函数的参数。此对象是一个键值对映射,用于向已保存状态写入对象以及从其中检索对象。这些值会在进程被系统终止后继续保留,并通过同一对象保持可用状态。
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
然后,您可以检索 ViewModel
的实例,而无需任何其他配置。默认 ViewModel
工厂会向您的 ViewModel
提供相应的 SavedStateHandle
。
class MainFragment : Fragment() {
val vm: SavedStateViewModel by viewModels()
...
}
提供自定义 ViewModelProvider.Factory
实例时,您可以通过扩展 AbstractSavedStateViewModelFactory
启用 SavedStateHandle
。
SavedState
与您的任务堆栈相关联。如果任务堆栈消失,SavedState
也会消失。强制停止应用、从“最近用过”菜单中移除应用或重新启动设备时,可能会发生这种情况。在这种情况下,任务堆栈会消失,并且您无法恢复SavedState
中的信息。请记住:在用户发起的界面状态解除情景中,不会恢复保存的状态。在系统发起的界面状态解除情景中,则会恢复。
要点:通常,保存的实例状态中存储的数据是临时状态,根据用户的输入或导航而定。这方面的例子包括:列表的滚动位置、用户想详细了解的项目的 ID、正在进行的用户偏好设置选择或文本字段的输入。
重要提示:要使用的 API 取决于状态存储的位置及其所需的逻辑。对于业务逻辑中使用的状态,请将其存储在 ViewModel 中,并使用 SavedStateHandle 保存。对于界面逻辑中使用的状态,请使用 View 系统中的 onSaveInstanceState API 或 Compose 中的 rememberSaveable。
注意:状态必须简单轻省。对于复杂或大型数据,应使用本地持久性存储。
SavedStateHandle 的使用
SavedStateHandle
类是一个键值对映射,通过 set()
和 get()
方法向已保存的状态写入数据以及从中检索数据。
通过使用 SavedStateHandle
,查询值会在进程终止后保留下来,从而确保用户在重新创建前后看到同一组过滤后的数据,而无需 activity 或 fragment 手动保存、恢复该值并将其重新转给 ViewModel。
注意:仅当 Activity 停止时,SavedStateHandle 才会保存写入其中的数据。在 Activity 停止时对 SavedStateHandle 的写入,只有在 Activity 收到 onStop 后,又再次收到 onStart 时,才会被保存。
SavedStateHandle
包含以下常用方法:
contains(String key)
- 检查是否存在给定键的值。remove(String key)
- 移除给定键的值。keys()
- 返回SavedStateHandle
中包含的所有键。
此外,还可以使用可观察数据存储器从 SavedStateHandle
检索值。支持的类型包括:
- LiveData。
- StateFlow。
- Compose 的状态 API。
通过 LiveData 从 SavedStateHandle 检索数据
使用 getLiveData()
方法从 SavedStateHandle
中检索以 LiveData
可观察对象形式封装的值。当键的值更新时,LiveData
会收到新值。通常,该值是因用户互动而设置的,例如输入查询来过滤数据列表。然后,您可以使用这个更新后的值转换 LiveData
。
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
val filteredData: LiveData<List<String>> = savedStateHandle.getLiveData<String>("query")
.switchMap { query -> repository.getFilteredData(query) }
fun setQuery(query: String) {
savedStateHandle["query"] = query
}
}
通过 StateFlow 从 SavedStateHandle 检索数据
使用 getStateFlow()
方法从 SavedStateHandle
中检索以 StateFlow
可观察对象形式封装的值。当您更新该键的值时,StateFlow
会收到新值。通常,您可能会因用户互动而设置该值,例如输入查询来过滤数据列表。然后,您可以使用其他 Flow
运算符转换此更新的值。
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
val filteredData: StateFlow<List<String>> = savedStateHandle.getStateFlow<String>("query")
.flatMapLatest { query -> repository.getFilteredData(query) }
fun setQuery(query: String) {
savedStateHandle["query"] = query
}
}
Compose 的状态支持
在lifecycle-viewmodel-compose
依赖中提供了实验性的 saveable
API,从而能够在 SavedStateHandle
和 Compose
的 Saver
之间实现互操作性,以便您能够通过 rememberSaveable
使用自定义 Saver
保存的任何 State
也可以通过 SavedStateHandle
保存。
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
var filteredData: List<String> by savedStateHandle.saveable {
mutableStateOf(emptyList())
}
fun setQuery(query: String) {
withMutableSnapshot {
filteredData += query
}
}
}
SavedStateHandle 支持的类型
保留在 SavedStateHandle
中的数据将作为 Bundle
与 activity
或 fragment
的 savedInstanceState
的其余部分一起保存和恢复。
默认情况下,您可以对 SavedStateHandle
调用 set()
和 get()
,以处理与 Bundle
相同的数据类型,SavedStateHandle
直接支持的类型如下所示:
类型/类支持 | 数组支持 |
---|---|
double | double[] |
int | int[] |
long | long[] |
String | String[] |
byte | byte[] |
char | char[] |
CharSequence | CharSequence[] |
float | float[] |
Parcelable | Parcelable[] |
Serializable | Serializable[] |
short | short[] |
SparseArray | |
Binder | |
Bundle | |
ArrayList | |
Size (only in API 21+) | |
SizeF (only in API 21+) |
如果要保持的数据类没有扩展上述列表中的任何一项,则应考虑通过添加 @Parcelize
Kotlin 注解或直接实现 Parcelable
来使该类变为 Parcelable
类。
保存非 Parcelable 类
如果某个类未实现 Parcelable
或 Serializable
且不能修改为实现这些接口之一,则无法直接将该类的实例保存到 SavedStateHandle
中。
从 Lifecycle 2.3.0-alpha03
开始,SavedStateHandle
允许您保存任何对象,具体方法是:使用 setSavedStateProvider()
方法提供您自己的逻辑用于将对象作为 Bundle
来保存和恢复。SavedStateRegistry.SavedStateProvider
是一个接口,用于定义单个 saveState()
方法来返回包含您希望保存的状态的 Bundle
。当 SavedStateHandle
准备好保存其状态后,它会调用 saveState()
以从 SavedStateProvider
检索 Bundle
,并为关联的键保存 Bundle
。
考虑一个示例应用,该应用通过 ACTION_IMAGE_CAPTURE
Intent 向相机应用请求图片,并传递一个临时文件,以供相机存储图片。TempFileViewModel
封装了创建该临时文件的逻辑:
class TempFileViewModel : ViewModel() {
private var tempFile: File? = null
fun createOrGetTempFile(): File {
return tempFile ?: File.createTempFile("temp", null).also {
tempFile = it
}
}
}
为确保临时文件在 activity
的进程终止随后又恢复后不会丢失,TempFileViewModel
可以使用 SavedStateHandle
保留其数据。如需允许 TempFileViewModel
保存其数据,请实现 SavedStateProvider
,并将其设置给 ViewModel
的 SavedStateHandle
:
private fun File.saveTempFile() = bundleOf("path", absolutePath)
class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
private var tempFile: File? = null
init {
savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
if (tempFile != null) {
tempFile.saveTempFile()
} else {
Bundle()
}
}
}
fun createOrGetTempFile(): File {
return tempFile ?: File.createTempFile("temp", null).also {
tempFile = it
}
}
}
如需在用户返回时恢复 File
数据,请从 SavedStateHandle
中检索 temp_file
Bundle。这正是 saveTempFile()
提供的包含绝对路径的 Bundle。该绝对路径随后可用于实例化新的 File。
private fun File.saveTempFile() = bundleOf("path", absolutePath)
private fun Bundle.restoreTempFile() = if (containsKey("path")) {
File(getString("path"))
} else {
null
}
class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
private var tempFile: File? = null
init {
val tempFileBundle = savedStateHandle.get<Bundle>("temp_file")
if (tempFileBundle != null) {
tempFile = tempFileBundle.restoreTempFile()
}
savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
if (tempFile != null) {
tempFile.saveTempFile()
} else {
Bundle()
}
}
}
fun createOrGetTempFile(): File {
return tempFile ?: File.createTempFile("temp", null).also {
tempFile = it
}
}
}
创建具有依赖项的 ViewModel
如果 ViewModel
类在其构造函数中接收依赖项,请提供用实现 ViewModelProvider.Factory
接口的工厂。并覆写 create(Class<T>, CreationExtras)
方法以提供 ViewModel
的新实例。
借助 CreationExtras
,您可以访问有助于实例化 ViewModel
的相关信息。下面列出了可以通过 extra
访问的键:
键 | 功能 |
---|---|
ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY | 提供传递给 ViewModelProvider.get() 的自定义键的访问权限。 |
ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY | 提供对 Application 类实例的访问权限。 |
SavedStateHandleSupport.DEFAULT_ARGS_KEY | 提供在构造 SavedStateHandle 时应使用的参数 bundle 的访问权限。 |
SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY | 提供用于构造 ViewModel 的 SavedStateRegistryOwner 的访问权限。 |
SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY | 提供用于构造 ViewModel 的 ViewModelStoreOwner 的访问权限。 |
如需创建 SavedStateHandle 的新实例,请使用 CreationExtras.createSavedStateHandle()
函数并将其传递给 ViewModel
。
以下示例说明了如何提供 ViewModel
的实例,该实例会将作用域限定为 Application
类的存储仓库和 SavedStateHandle
作为依赖项:
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras
class MyViewModel(
private val myRepository: MyRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// ViewModel logic
// ...
// Define ViewModel factory in a companion object
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
// Get the Application object from extras
val application = checkNotNull(extras[APPLICATION_KEY])
// Create a SavedStateHandle for this ViewModel from extras
val savedStateHandle = extras.createSavedStateHandle()
return MyViewModel((application as MyApplication).myRepository, savedStateHandle) as T
}
}
}
}
或者,也可选择使用 ViewModel factory DSL 创建工厂:
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
class MyViewModel(
private val myRepository: MyRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// ViewModel logic
// Define ViewModel factory in a companion object
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val savedStateHandle = createSavedStateHandle()
val myRepository = (this[APPLICATION_KEY] as MyApplication).myRepository
MyViewModel(myRepository, savedStateHandle)
}
}
}
}
注意:如果 ViewModel 不接受任何依赖项,或只将 SavedStateHandle 类型作为依赖项,您便无需为框架提供工厂来实例化该 ViewModel 类型。
注意:如果在注入 ViewModel 时使用 Hilt 作为依赖项注入解决方案,您便无需手动定义 ViewModel 工厂。Hilt 会生成一个工厂,它知道如何在编译时为您创建所有带有 @HiltViewModel 注解的 ViewModel。调用常规 ViewModel API 时,带有 @AndroidEntryPoint 注解的类可以直接访问 Hilt 生成的工厂。
如果多个ViewModel
共用相同的依赖项时,可以使用同一个 ViewModel
工厂,请参考此示例。
然后,就可以在Activity
中调用ViewModelProvider
工厂来创建ViewModel
实例:
import androidx.activity.viewModels
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }
......
}
ViewModel Best practices
以下是实现 ViewModel 时应遵循的一些重要的最佳实践:
- 由于 ViewModel 的作用域,请使用 ViewModel 作为屏幕级状态容器的实现细节。请勿将它们用作可重复使用的界面组件的状态容器,如表单组件等。否则,当您在同一 ViewModelStoreOwner 下将同一界面组件用于不同用途时,您会获取相同的 ViewModel 实例。
- ViewModel 不应该知道界面实现细节。请尽可能对 ViewModel API 公开的方法和界面状态字段使用通用名称。
- 由于 ViewModel 的生命周期可能比 ViewModelStoreOwner 更长,因此 ViewModel 不应保留任何对与生命周期相关的 API(例如 Context 或 Resources)的引用,以免发生内存泄漏。
- 请勿将 ViewModel 传递给其他类、函数或其他界面组件。由于平台会管理它们,因此您应该使其尽可能靠近平台。应该靠近您的 activity、fragment 或屏幕级可组合函数。这样可以防止较低级别的组件访问超出其需求的数据和逻辑。
ViewModel 配置变更后的复用原理
更准确的说是,ViewModel
是如何做到宿主销毁了,还能继续存在。以至于页面恢复重建后,还能接着复用。(肯定是前后获取到的同一个ViewModel
实例)
不管是 by viewModels { MyViewModel.Factory }
方式还是 ViewModelProvider(this).get(MyViewModel::class.java)
方式创建的 ViewModel
最终都是会通过 ViewModelProvider().get()
方式来获取 ViewModel
。
by viewModels()
语法最终会调用到如下代码:
而 ViewModelProvider(this).get(MyViewModel::class.java)
方式最终也会调用到ViewModelProvider
的三个参数的主构造方法,并从ViewModelStoreOwner
对象中获取viewModelStore
对象:
ViewModelProvider
的get
方法:
internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName ?: throw IllegalArgumentException("...")
return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
val viewModel = store[key] // store就是构造函数传入的`viewModelStore`对象
if (modelClass.isInstance(viewModel)) {
(factory as? OnRequeryFactory)?.onRequery(viewModel)
return viewModel as T
} else {
......
}
val extras = MutableCreationExtras(defaultCreationExtras)
extras[VIEW_MODEL_KEY] = key
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) }
}
// ViewModelStore.java
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {...... }
final ViewModel get(String key) {
return mMap.get(key);
}
public final void clear() { ...... }
}
可知,ViewModelProvider
内部是通过ViewModelStore
获取的 ViewModel
缓存对象的,而ViewModelStore
内部使用一个map
存储ViewModel
实例,每次从map
中查询,key
是ViewModel的包名+类名。如果ViewModelStore
的map
中能够获取到ViewModel
实例就直接返回,否则就会调用factory
工厂的create
方法来创建一个ViewModel
实例并保存到ViewModelStore
的map
中。
所以ViewModel
的复用本质是对ViewModelStore
对象的复用。
通过 ViewModelProvider
一个参数的构造函数的方式获取ViewModelStore
对象是通过调用ViewModelStoreOwner
接口的getViewModelStore()
方法。
ViewModelStoreOwner
接口中只有一个 getViewModelStore()
方法:
public interface ViewModelStoreOwner {
ViewModelStore getViewModelStore();
}
前面介绍过ComponentActivity
是ViewModelStoreOwner
接口的直接子类之一,因此它肯定实现了 getViewModelStore()
方法。所以通过 ViewModelProvider(this).get()
的使用方式也合情合理。
而通过 by viewModels()
语法创建ViewModel
时,由于viewModels()
是 ComponentActivity
的一个扩展方法,所以可以直接调用 ComponentActivity
的getViewModelStore()
方法来获取 ViewModelStore
对象。
看一下 ComponentActivity
的getViewModelStore()
方法源码实现:
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException(......);
}
ensureViewModelStore();
return mViewModelStore;
}
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore; // 从NonConfiguration配置中读取viewModelStore
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
由上面源码可知,ViewModelStore
是存储在一个配置对象中的(NonConfiguration
)。
当一个页面因为配置变更会重新走ActivityThread
的handleRelaunchActivity
方法,这个方法中
会把因配置变更销毁的Activity
的配置信息提取出来保存,并在重新创建新的Activity
时,通过attach
方法再次回传回去。
final void attach(Context context, ActivityThread aThread,
...,
NonConfigurationInstances lastNonConfigurationInstances, ...) {
......
mLastNonConfigurationInstances = lastNonConfigurationInstances;
......
}
因此在Activity
中能够通过配置信息对象NonConfiguration
获取到之前的ViewModelStore
实例对象。
Activity
中提取配置信息的方法:
// Activity.java
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
......
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
......
return nci;
}
// ComponentActivity.java
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
......
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
可以看到,viewModelStore
对象被保存到了NonConfiguration
对象中。
具体地来说,ActivityThread.handleRelaunchActivity
会调用handleDestroyActivity
方法先销毁之前的Activity
,而在handleDestroyActivity
方法中就会调用上面的retainNonConfigurationInstances()
方法收集配置信息:
// ActivityThread.java
private void handleRelaunchActivityInner(...) {
......
handleDestroyActivity(...);
......
handleLaunchActivity(...);
}
@Override
public void handleDestroyActivity(...) {
performDestroyActivity(...);
......
}
void performDestroyActivity(......) {
......
performPauseActivityIfNeeded(r, "destroy");
if (!r.stopped) {
callActivityOnStop(r, false /* saveState */, "destroy");
}
if (getNonConfigInstance) {
try {
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
......
}
}
try {
mInstrumentation.callActivityOnDestroy(r.activity);
......
} catch (Exception e) {
......
}
......
}
performLaunchActivity
中的调用:
// ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
......
} catch (Exception e) {
......
}
......
if (activity != null) {
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
r.assistToken, r.shareableActivityToken);
......
}
......
return activity;
}
在上面代码中 activity.attach()
方法通过 r.lastNonConfigurationInstances
参数回传了之前保存的NonConfiguration
配置信息。
总结:
- ViewModel实现复用的本质是
ViewModelStore
对象以NonConfigurationInstances
的形式存储到ActivityClientRecord
中,在Activity
重建后,又被传递到新创建的Activity
对象中(通过activity.attach方法)。
SavedStateHandle 数据存储与复用原理解析
- SavedStateHandle:负责单个ViewModel的数据存储与恢复
- SavedStateRegistry:数据存储、恢复中心
- SavedStateRegistryController:用于创建 SavedStateRegistry
SavedStateRegistry模型:一个总Bundle
, key-value
存储每个ViewModel
的子Bundle
- SavedState数据存储流程:逐一调用每一个
SavedStateHandle
保存自己的数据,汇总成一个总的Bundle
, 再存储到Activity
的savedState
对象中
- SavedState数据复用流程(1):从
Activity
的重建时传入的savedState
恢复所有ViewModel
的数据到SavedStateRegistry
- SavedState数据复用流程(2):创建
ViewModel
并传递恢复的SavedStateHandle
总结:
SavedStateHandle是如何实现数据复用的:
- SavedState本质是利用了 onSaveInstanceState()的时机。每个ViewModel 的数据单独存储在一个Bundle再合并成一个整体后,再存储到Activity的outBundle中。
- 数据最终会存储到ActivityRecord中,页面重建后,又被传递到新的ViewModel 对象中。
ViewModel 和 onSaveInstanceState() 方法有什么区别
- onSaveInstanceState() 只能存储轻量级的key-value键值对数据,非配置变更导致的页面被回收时才会触发,此时数据存储在ActivityRecord中
- ViewModel 可以存放任意Objedct数据。因配置变更导致的页面被回收时才有效。此时在ActivityThread#ActivityClientRecord中。
保存界面状态
在系统发起的 activity 销毁或应用销毁操作前后,及时保存和恢复 activity 的界面状态,这是用户体验中一个至关重要的部分。在这些情况下,用户希望界面状态保持不变,但是系统会销毁 activity 及其中存储的任何状态。
为了让系统行为符合用户预期,可以将 ViewModel 对象、保存的实例状态 saved Instance state(包括 View 系统中的 onSaveInstanceState() API、Jetpack Compose 中的 rememberSaveable 和 ViewModel 中的 SavedStateHandle)和/或本地存储空间组合起来使用,从而在发生此类应用和 Activity 实例转换后保持界面状态。在决定如何组合这些选项时,需要考虑界面数据的复杂程度、应用的使用场景以及数据获取速度与内存用量的权衡。
无论采用哪种方法,都应确保应用满足用户对其界面状态的预期,并提供流畅、简洁的界面(消除将数据载入界面过程中的延迟,尤其是在发生像旋转这样频繁的配置更改之后)。大多数情况下,为了在应用中提供最佳的用户体验,需要组合使用多种不同的方法。
根据执行的操作,用户会希望系统清除或保留 activity 状态。在某些情况下,系统会自动执行用户预期的操作,但有时系统会执行与用户预期相反的操作。
用户发起的界面状态解除
用户希望当他们启动 activity
时,该 activity
的暂时性界面状态会保持不变,直到用户完全关闭 activity
为止。用户可通过执行以下操作来完全关闭 activity:
- 从“最近使用的应用” 列表中滑动关闭
activity
。 - 从系统设置页面中终止或强制退出应用。
- 重新启动设备。
- 正常执行某种“退出”操作(触发
Activity.finish()
)。
在这些完全关闭的情况下,用户会认为他们已经永久离开 activity
,如果他们重新打开 activity
,会希望 activity
以干净的状态启动。系统在这些关闭场景中的基础行为符合用户预期,即 activity
实例将连同其中存储的任何状态以及与该 activity
关联的任何已保存实例状态记录一起被销毁并从内存中移除。
这条关于完全关闭的规则有一些例外情况,例如用户在浏览器中按返回按钮时可能希望跳转到他们之前浏览的网页,而不是退出浏览器。
系统发起的界面状态解除
用户期望 activity 的界面状态在整个配置更改(例如旋转或切换到多窗口模式)期间保持不变。但是,默认情况下,系统会在发生此类配置更改时销毁 activity,从而清除存储在 activity 实例中的任何界面状态。
如果用户暂时切换到其他应用,稍后再返回到您的应用,他们也会希望 activity 的界面状态保持不变。例如,用户在您的搜索 activity 中执行搜索,然后按主屏幕按钮或接听电话,当他们返回搜索 activity 时,希望看到搜索关键字和结果仍在原处,并和之前完全一样。
在这种情况下,您的应用会被置于后台,系统会尽最大努力将您的应用进程留在内存中。但是,当用户转而去与其他应用进行互动时,系统可能会销毁您的应用进程。在这种情况下,activity 实例连同其中存储的任何状态都会一起被销毁。当用户重新启动应用时,activity 会出乎意料地处于干净状态。
用于保留界面状态的选项
当用户对界面状态的预期与默认系统行为不符时,您需要保存并恢复用户的界面状态,以确保系统发起的销毁对用户完全透明。
按照以下几个会影响用户体验的维度考量,用于保留界面状态的每个选项都有所差异:
ViewModel | 保存的实例状态 | 永久性存储空间 | |
---|---|---|---|
存储位置 | 在内存中 | 在内存中 | 在磁盘或网络上 |
在配置更改后继续存在 | 是 | 是 | 是 |
在系统发起的进程终止后继续存在 | 否 | 是 | 是 |
在用户完全关闭 activity 或触发 onFinish() 后继续存在 | 否 | 否 | 是 |
数据限制 | 支持复杂对象,但是空间受可用内存的限制 | 仅适用于基础类型和简单的小对象,例如字符串 | 仅受限于磁盘空间或从网络资源检索的成本/时间 |
读取/写入时间 | 快(仅限内存访问) | 慢(需要序列化/反序列化和磁盘访问) | 慢(需要磁盘访问或网络事务) |
注意:上表中的保存的实例状态包括 onSaveInstanceState() 和 rememberSaveable API,以及 SavedStateHandle(作为 ViewModel 的一部分)。
使用 ViewModel 处理配置更改
ViewModel 非常适合在用户正活跃地使用应用时存储和管理界面相关数据。它支持快速访问界面数据,并且有助于避免在发生旋转、窗口大小调整和其他常见的配置更改后从网络或磁盘中重新获取数据。
ViewModel 将数据保留在内存中,这意味着开销要低于从磁盘或网络检索数据。ViewModel 与一个 activity(或其他某个生命周期所有者)相关联,在配置更改期间保留在内存中,系统会自动将 ViewModel 与发生配置更改后产生的新 activity 实例相关联。
当用户退出您的 activity 或 fragment 时,或者在您调用 finish() 的情况下,系统会自动销毁 ViewModel,这意味着状态会被清除,正如用户在这些场景中所预期的一样。
与保存的实例状态不同,ViewModel 在系统发起的进程终止过程中会被销毁。如需在 ViewModel 中由系统发起的进程终止结束后重新加载数据,请使用 SavedStateHandle API。或者,如果数据与界面相关,不需要存储在 ViewModel 中,请使用 onSaveInstanceState()
(在 View 系统中)或 rememberSaveable
(在 Jetpack Compose 中)。如果数据是应用数据,最好将其保存到磁盘。
如果您已有用于在发生配置更改后存储界面状态的内存中解决方案,则可能不需要使用 ViewModel。
使用保存的实例状态作为后备方法来处理系统发起的进程终止
View 系统中的 onSaveInstanceState()
回调、Jetpack Compose 中的 rememberSaveable
以及 ViewModel 中的 SavedStateHandle
会存储一些数据(如 activity
或 fragment
),以供系统在销毁界面控制器后重新创建时,用于重新加载界面控制器的状态。
保存的实例状态的 bundles
在配置更改和进程终止后会保留下来,但会因不同的 API 将数据序列化到磁盘,而受存储容量和速度的限制。如果序列化的对象很复杂,序列化会占用大量的内存。因为此过程在配置更改期间发生在主线程上,所以长时间运行的序列化可能会导致丢帧和视觉卡顿。
要点:saved Instance state API 仅在 Activity 停止时才会保存写入其中的数据。如果在此生命周期状态之间写入数据,则保存操作将推迟到下一个停止的生命周期事件。
保存的实例状态不应用于存储大量数据(如Bitmap),或需要冗长的序列化或反序列化操作的复杂数据结构。而只能用于存储基础类型和简单的小型对象,例如 String。因此,请使用保存的实例状态来存储最少量的必要数据(例如 ID),以便在其他保留机制失败时重新创建必要的数据,将界面恢复到以前的状态。大多数应用都应实现此机制来处理系统发起的进程终止。
根据应用的使用场景,您可能完全不需要使用保存的实例状态。例如,浏览器可能会将用户带回他们在退出浏览器之前正在查看的确切网页。如果 activity 表现出这种行为,您可以放弃使用保存的实例状态,改为在本地保留所有内容。
要点:通常,保存的实例状态中存储的数据是临时状态,根据用户的输入或导航而定。这方面的例子包括:列表的滚动位置、用户想详细了解的项目的 ID、正在进行的用户偏好设置选择或文本字段的输入。
此外,如果您从 intent
打开 activity
,当配置发生更改以及系统恢复该 activity 时,会将 extra bundles
传送给该 activity。在 activity 启动时,如果一段界面状态数据(例如搜索查询)作为 intent extra
传入,则您可以使用 extra bundles
而不是保存的实例状态bundles
。
在上述任一情况下,您仍然可以使用 ViewModel
来避免因在配置更改期间从数据库重新加载数据而浪费周期时间。
如果要保留的是简单的轻量级界面数据,那么您可以单独使用保存的实例状态 API 来保留状态数据。
要点:要使用的 API 取决于状态存储的位置及其所需的逻辑。对于业务逻辑中使用的状态,请将其存储在 ViewModel 中,并使用 SavedStateHandle 保存。对于界面逻辑中使用的状态,请使用 View 系统中的 onSaveInstanceState API 或 Compose 中的 rememberSaveable。
使用 onSaveInstanceState() 保存简单轻量的界面状态
当您的 Activity
开始停止时,系统会调用 onSaveInstanceState()
方法,以便您的 Activity
可以将状态信息保存到实例状态 Bundle
中。此方法的默认实现保存有关 Activity
视图层次结构状态的瞬时信息,例如 EditText
控件中的文本或 ListView
控件的滚动位置。
如需保存 Activity
的实例状态信息,必须覆写 onSaveInstanceState()
方法,并将key-value
键值对添加到您的 Activity
意外销毁时所保存的 Bundle
对象中。覆写 onSaveInstanceState()
时,如果您希望默认实现保存视图层次结构的状态,必须调用父类实现。例如:
override fun onSaveInstanceState(outState: Bundle?) {
// Save the user's current game state
outState?.run {
putInt(STATE_SCORE, currentScore)
putInt(STATE_LEVEL, currentLevel)
}
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(outState)
}
companion object {
val STATE_SCORE = "playerScore"
val STATE_LEVEL = "playerLevel"
}
注意:当用户显式关闭 Activity 时,或者在其他情况下调用 finish() 时,系统不会调用 onSaveInstanceState()。
如需保存持久性数据(例如用户首选项或数据库中的数据),您应在 Activity
位于前台时抓住合适机会。如果没有这样的时机,您应在执行 onStop()
方法期间保存此类数据。
使用 savedInstanceState 恢复 Activity 界面状态
重建先前被销毁的 Activity 后,您可以从系统传递给 Activity 的 Bundle 中恢复保存的实例状态。onCreate()
和 onRestoreInstanceState()
回调方法均会收到包含实例状态信息的相同 Bundle
。
因为无论系统是新建 Activity 实例还是重新创建之前的实例,都会调用 onCreate() 方法,所以在尝试读取之前,您必须检查状态 Bundle 是否为 null
。如果为 null
,系统将新建 Activity
实例,而不会恢复之前销毁的实例。
例如,以下代码段显示如何在 onCreate()
中恢复某些状态数据:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
with(savedInstanceState) {
// Restore value of members from saved state
currentScore = getInt(STATE_SCORE)
currentLevel = getInt(STATE_LEVEL)
}
} else {
// Probably initialize members with default values for a new instance
}
// ...
}
你可以选择实现系统在 onStart()
方法之后调用的 onRestoreInstanceState()
方法,而不是在 onCreate()
期间恢复状态。仅当存在要恢复的已保存状态时,系统才会调用 onRestoreInstanceState()
,因此您无需检查 Bundle
是否为 null
:
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState)
// Restore state members from saved instance
savedInstanceState?.run {
currentScore = getInt(STATE_SCORE)
currentLevel = getInt(STATE_LEVEL)
}
}
注意:您应始终调用 onRestoreInstanceState() 的父类实现,以便默认实现可以恢复视图层次结构的状态。
使用 SavedStateRegistry 接入已保存状态
从 Fragment 1.1.0
或其传递依赖项 Activity 1.0.0
开始,界面控制器(例如 Activity
或 Fragment
)会实现 SavedStateRegistryOwner
并提供绑定到该控制器的 SavedStateRegistry
。SavedStateRegistry
允许组件连接到界面控制器的已保存状态,以使用后者或向其提供内容。例如,ViewModel
的已保存状态模块使用 SavedStateRegistry
创建 SavedStateHandle
并将其提供给您的 ViewModel
对象。您可以通过调用 getSavedStateRegistry()
从界面控制器中检索 SavedStateRegistry
。
对已保存状态提供内容的组件必须实现 SavedStateRegistry.SavedStateProvider
,后者定义了一个名为 saveState()
的方法。saveState()
方法允许组件返回 Bundle
,其中包含应从该组件保存的任何状态。SavedStateRegistry
在界面控制器生命周期的保存状态阶段调用此方法。
重要提示:仅当 Activity 停止时,SavedStateHandle 才会保存写入其中的数据。在 Activity 停止时对 SavedStateHandle 的写入,只有在 Activity 收到 onStop 后,又再次收到 onStart 时,才会被保存。
class SearchManager : SavedStateRegistry.SavedStateProvider {
companion object {
private const val QUERY = "query"
}
private val query: String? = null
...
override fun saveState(): Bundle {
return bundleOf(QUERY to query)
}
}
如需注册 SavedStateProvider
,请对 SavedStateRegistry
调用 registerSavedStateProvider()
,并传递一个与提供程序的数据以及提供程序相关联的密钥。对 SavedStateRegistry
调用 consumeRestoredStateForKey()
并传入与提供程序数据关联的密钥即可从已保存状态中检索之前为提供程序保存的数据。
在 Activity
或 Fragment
中,您可以在 onCreate()
中调用 super.onCreate()
后注册 SavedStateProvider
。或者,您也可以对 SavedStateRegistryOwner
(实现了 LifecycleOwner
的)设置一个 LifecycleObserver
,然后在 LifecycleObserver
的 ON_CREATE
事件发生后立即注册 SavedStateProvider
。通过使用 LifecycleObserver
,可以使得将保存状态的注册和检索从 SavedStateRegistryOwner
本身分离。
class SearchManager(registryOwner: SavedStateRegistryOwner) : SavedStateRegistry.SavedStateProvider {
companion object {
private const val PROVIDER = "search_manager"
private const val QUERY = "query"
}
private val query: String? = null
init {
// Register a LifecycleObserver for when the Lifecycle hits ON_CREATE
registryOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_CREATE) {
val registry = registryOwner.savedStateRegistry
// Register this object for future calls to saveState()
registry.registerSavedStateProvider(PROVIDER, this)
// Get the previously saved state and restore it
val state = registry.consumeRestoredStateForKey(PROVIDER)
// Apply the previously saved state
query = state?.getString(QUERY)
}
}
}
override fun saveState(): Bundle {
return bundleOf(QUERY to query)
}
...
}
class SearchFragment : Fragment() {
private var searchManager = SearchManager(this)
...
}
针对复杂或大型数据使用本地持久性存储来处理进程终止
只要您的应用安装在用户的设备上,持久性本地存储(例如数据库或共享偏好设置)就会继续存在(除非用户清除应用的数据)。虽然此类本地存储空间会在系统启动的活动和应用进程终止后继续存在,但由于必须从本地存储空间读取到内存,因此检索成本高昂。这种永久性本地存储空间通常已经是应用架构的一部分,用于存储您打开和关闭 activity 时不想丢失的所有数据。
ViewModel 和已保存实例状态均不是长期存储解决方案,因此不能替代本地存储空间,例如数据库。您只应该使用这些机制来暂时存储瞬时界面状态,对于其他应用数据,应使用永久性存储空间。
管理界面状态:分而治之
您可以通过在各种类型的保留机制之间划分工作,高效地保存和恢复界面状态。在大多数情况下,这些机制中的每一种都应存储 activity 中使用的不同类型的数据,具体取决于数据复杂度、访问速度和生命周期的权衡:
- 本地持久性存储:存储在您打开和关闭 activity 时不希望丢失的所有应用数据。示例:歌曲对象的集合,其中可能包括音频文件和元数据。
- ViewModel:将显示关联界面所需的所有数据(即屏幕界面状态)存储在内存中。示例:最近搜索的歌曲对象和最近的搜索查询。
- 保存的实例状态:存储少量的数据,以便在系统停止界面后又重新创建时,用于轻松重新加载界面状态。这里不存储复杂对象,而是将复杂对象保留在本地存储空间中,并将这些对象的唯一 ID 存储在保存的实例状态 API 中。示例:存储最近的搜索查询。
例如,假设有一个用于搜索歌曲库的 activity。应按如下方式处理不同的事件:
-
当用户添加歌曲时,ViewModel 会立即委托在本地保留此数据。如果新添加的这首歌曲应显示在界面中,则您还应更新 ViewModel 对象中的数据以表明该歌曲已添加。切记在主线程以外执行所有数据库插入操作。
-
当用户搜索歌曲时,从数据库加载的任何复杂歌曲数据都应作为屏幕界面状态的一部分立即存储在 ViewModel 对象中。
-
当 activity 进入后台且系统调用保存的实例状态 API 时,应将搜索查询存储在保存的实例状态中,以备进程重新创建时使用。由于加载在此过程中保留下来的应用数据需要用到搜索查询,因此应将其存储在 ViewModel SavedStateHandle 中。这些就是加载数据并让界面恢复到当前状态所需的所有信息。
恢复复杂的状态:重组碎片
当到了用户该返回 Activity 的时候,重新创建 Activity 存在两种可能情况:
- 在系统停止 activity 后,需要重新创建该 activity。系统已将查询保存在
savedInstanceState
bundles
中,如果未使用SavedStateHandle
,则界面应将查询传递给ViewModel
。ViewModel
看到没有缓存任何搜索结果时,会委托使用指定的搜索查询加载搜索结果。 - 在配置更改后创建 activity。由于
ViewModel
实例尚未销毁,因此ViewModel
会将所有信息缓存在内存中,而无需重新查询数据库。