Jetpack架构组件库:Lifecycle、LiveData、ViewModel

news2024/11/27 21:04:02

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 类分派的生命周期事件。这些事件映射到 activityfragment 中的回调事件。
  • 状态:由 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()),该方法必须由类实现。 此接口从各个类(如 FragmentAppCompatActivity)抽象化 Lifecycle 的所有权,并允许编写与这些类搭配使用的组件。任何自定义应用类均可实现 LifecycleOwner 接口。

实现 DefaultLifecycleObserver 的组件可与实现 LifecycleOwner 的组件完美配合,因为所有者可以提供生命周期,而观察者可以注册以观察生命周期。

对于位置跟踪示例,我们可以让 MyLocationListener 类实现 DefaultLifecycleObserver,然后在 onCreate() 方法中使用 activityLifecycle 对其进行初始化。这样,MyLocationListener 类便可以“自给自足”,这意味着,对生命周期状态的变化做出响应的逻辑会在 MyLocationListener(而不是在 activity)中进行声明。让各个组件存储自己的逻辑可使 activityfragment 逻辑更易于管理。

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 及更高版本中的 FragmentActivity 均已实现 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的源码流程进行了简要分析。

在这里插入图片描述

简要总结:

  • ActivitygetLifecycle 方法返回的是一个 LifecycleRegistry 对象,LifecycleRegistry类正是Lifecycle接口的实现者。
  • LifecycleRegistry通过弱引用持有了LifecycleOwner的对象,也就是Activity对象 。
  • ActivityonCreate()方法中添加了一个透明的 ReportFragment 来专门处理生命周期。
  • ReportFragment 中, API >= 29 时的处理逻辑是,调用 LifecycleCallbacks.registerIn(activity) 方法, 其内容是:为Activity注册一个Application.ActivityLifecycleCallbacks的回调接口实现类,这个CallbackActvity内部保存了起来。在 ATMS 跨进程调用ActivityThread中的 handleStartActivity方法时,回调ActivityperformStart()方法,进而回调其保存的Callback的回调方法,在其中拿到Activity持有的LifecycleRegistry对象进行分发处理,最终调用到LifecycleEventObserver观察者对象的onStateChanged()接口方法。
  • ReportFragment 中, API < 29 时的处理逻辑是, 在ReportFragment的生命周期方法里,进行事件分发,进而回调LifecycleEventObserveronStateChanged()接口方法。
  • 普通的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的前后台切换。

当应用启动时会依次调用:

  1. MyApp --> onCreate
  2. MyApp --> onStart
  3. MyApp --> onResume

当应用按Home键返回桌面或者切换到最近应用列表界面时会依次调用:

  1. MyApp --> onPause
  2. MyApp --> onStop

当从界面返回到应用时会依次调用:

  1. MyApp --> onStart
  2. MyApp --> onResume

可以看到只有应用第一次创建时会回调 onCreate 方法,后面应用前后台切换时会在 onPause/onStoponStart/onResume 两组回调方法之间切换,可以根据需要选择对应的回调方法进行监听业务处理。

LiveData

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 activity、fragment 或 service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者

如果观察者(由 Observer 类表示)的生命周期处于 STARTEDRESUMED 状态,则 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

如果观察者的生命周期处于 STARTEDRESUMED 状态,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将通过 FragmentgetViewLifecycleOwner() 方法来获取 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,它们分别为liveData1liveData2,我们希望将它们观察的数据合并到一个 MediatorLiveData 类型的 liveDataMerger 对象中,然后liveData1liveData2将成为 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对象进行注册。

传入 LifecycleBoundObserverobserver被保存在它的父类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会导致当前 LiveDatamVersion的值从初始值-1变成0,而后注册的Observer监听对象的mLastVersion的值还是初始值-1,因此走到这里时,判断条件observer.mLastVersion >= mVersion不满足,会继续往下走,执行进行回调。并且会将Observer监听对象的mLastVersionmVersion进行对齐,表示已经分发过了,在下次再次进入considerNotify方法时,就会走return逻辑。

因此解决方法就是在LiveData注册观察者Observer时,将ObservermLastVersion版本号修改为大于等于LiveDatamVersion 版本号。

可以继承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 的作用域将限定为 ViewModelStoreOwnerLifecycle。它会一直保留在内存中,直到其 ViewModelStoreOwner 永久消失。

ViewModelStoreOwner 接口的直接子类为 ComponentActivityFragmentNavBackStackEntry。如需查看ViewModelStoreOwner 接口的间接子类的完整列表,请参阅 ViewModelStoreOwner 参考文档。

ViewModel 的作用域 fragmentactivity 被销毁时,异步工作会在作用域限定到该 fragmentactivityViewModel 中继续进行,这是持久性的关键。

对业务逻辑的访问权限

尽管绝大多数业务逻辑都存在于数据层中,但界面层也可以包含业务逻辑。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 通常不应引用ViewLifecycle 或可能存储对 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 依赖项

ViewModelStoreOwnerViewModel 的生命周期内销毁 ViewModel 时,ViewModel 会调用 onCleared 方法。这样,您就可以清理遵循 ViewModel 生命周期的任何工作或依赖项。

以下示例展示了 viewModelScope 的替代方法。 viewModelScope 是一个内置 CoroutineScope,会自动遵循 ViewModel 的生命周期。ViewModel 使用 viewModelScope 触发与业务相关的操作。如果您想使用自定义作用域(而不是 viewModelScope)使测试更简单,ViewModel 可以在其构造函数中接收 CoroutineScope 作为依赖项。如果 ViewModelStoreOwnerViewModel 的生命周期结束时清除 ViewModelViewModel 也会取消 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() 方法,可以获取作用域限定为任何 ViewModelStoreOwnerViewModel 实例。

将 ViewModel 的作用域限定为最近的 ViewModelStoreOwner

要将 ViewModel 的作用域限定为最近的 ViewModelStoreOwner非常简单,只需要借助 ActivityFragmentNavigation 提供的 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。以下示例展示了如何获取作用域限定为父 fragmentViewModel 实例:

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 获取作用域限定为 activityViewModel 是一种常见用例。为此,可以使用 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 FragmentNavigation 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 之外,还可以使用 HilthiltNavGraphViewModels(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,从而能够在 SavedStateHandleComposeSaver 之间实现互操作性,以便您能够通过 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 中的数据将作为 BundleactivityfragmentsavedInstanceState 的其余部分一起保存和恢复。

默认情况下,您可以对 SavedStateHandle 调用 set()get(),以处理与 Bundle 相同的数据类型,SavedStateHandle 直接支持的类型如下所示:

类型/类支持数组支持
doubledouble[]
intint[]
longlong[]
StringString[]
bytebyte[]
charchar[]
CharSequenceCharSequence[]
floatfloat[]
ParcelableParcelable[]
SerializableSerializable[]
shortshort[]
SparseArray
Binder
Bundle
ArrayList
Size (only in API 21+)
SizeF (only in API 21+)

如果要保持的数据类没有扩展上述列表中的任何一项,则应考虑通过添加 @Parcelize Kotlin 注解或直接实现 Parcelable 来使该类变为 Parcelable 类。

保存非 Parcelable 类

如果某个类未实现 ParcelableSerializable 且不能修改为实现这些接口之一,则无法直接将该类的实例保存到 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,并将其设置给 ViewModelSavedStateHandle

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对象:
在这里插入图片描述

ViewModelProviderget方法:

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的包名+类名。如果ViewModelStoremap中能够获取到ViewModel实例就直接返回,否则就会调用factory工厂的create方法来创建一个ViewModel实例并保存到ViewModelStoremap中。
在这里插入图片描述

所以ViewModel的复用本质是对ViewModelStore对象的复用。

通过 ViewModelProvider一个参数的构造函数的方式获取ViewModelStore对象是通过调用ViewModelStoreOwner接口的getViewModelStore() 方法。

ViewModelStoreOwner 接口中只有一个 getViewModelStore() 方法:

public interface ViewModelStoreOwner {
    ViewModelStore getViewModelStore();
}

前面介绍过ComponentActivityViewModelStoreOwner 接口的直接子类之一,因此它肯定实现了 getViewModelStore() 方法。所以通过 ViewModelProvider(this).get() 的使用方式也合情合理。

而通过 by viewModels() 语法创建ViewModel时,由于viewModels()ComponentActivity的一个扩展方法,所以可以直接调用 ComponentActivitygetViewModelStore() 方法来获取 ViewModelStore对象。

看一下 ComponentActivitygetViewModelStore() 方法源码实现:

@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)。

当一个页面因为配置变更会重新走ActivityThreadhandleRelaunchActivity方法,这个方法中
会把因配置变更销毁的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, 再存储到ActivitysavedState对象中

在这里插入图片描述

  • 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 中的 rememberSaveableViewModel 中的 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 会存储一些数据(如 activityfragment),以供系统在销毁界面控制器后重新创建时,用于重新加载界面控制器的状态。

保存的实例状态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 后,您可以从系统传递给 ActivityBundle 中恢复保存的实例状态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 开始,界面控制器(例如 ActivityFragment)会实现 SavedStateRegistryOwner 并提供绑定到该控制器的 SavedStateRegistrySavedStateRegistry 允许组件连接到界面控制器的已保存状态,以使用后者或向其提供内容。例如,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() 并传入与提供程序数据关联的密钥即可从已保存状态中检索之前为提供程序保存的数据。

ActivityFragment 中,您可以在 onCreate() 中调用 super.onCreate() 后注册 SavedStateProvider。或者,您也可以对 SavedStateRegistryOwner (实现了 LifecycleOwner 的)设置一个 LifecycleObserver,然后在 LifecycleObserverON_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,则界面应将查询传递给 ViewModelViewModel 看到没有缓存任何搜索结果时,会委托使用指定的搜索查询加载搜索结果。
  • 在配置更改后创建 activity。由于 ViewModel 实例尚未销毁,因此 ViewModel 会将所有信息缓存在内存中,而无需重新查询数据库。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/180487.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决nes_py在pip安装报错的问题

目录 项目场景&#xff1a; 问题描述 原因分析&#xff1a; 解决方案&#xff1a; 解决结果&#xff1a; 项目场景&#xff1a; 想跟随油管某视频复现强化学习方法玩超级马里奥的过程&#xff0c;结果在在Anaconda3虚拟环境中用pip安装nes_py时一直报错&#xff0c;报错信…

基于Python实现的图像文字识别OCR工具,包含GUI界面附完整版代码可直接运行

引言 最近在技术交流群里聊到一个关于图像文字识别的需求,在工作、生活中常常会用到,比如票据、漫画、扫描件、照片的文本提取。 博主基于 PyQt + labelme + PaddleOCR 写了一个桌面端的 OCR 工具,用于快速实现图片中文本区域自动检测 + 文本自动识别。 识别效果如下图所示:…

总结JDK中的时间日期类

在学习SpringMVC时&#xff0c;遇到了接收时间日期类型的参数的案例。 回顾JDK中与时间日期相关的API。 来系统地学习一下日期时间相关的API。 前置知识 在世界上有统一的时间标准 格林尼治时间&#xff0c;简称GMT&#xff08;以伦敦的本初子午线为标准&#xff09;&#x…

【BTC】数据结构

BTC 中对交易数据的存储主要涉及到了两种数据结构&#xff0c;一种是区块链&#xff0c;一种是 Merkle Tree。这两种数据结构组成了 BTC 中完整的区块链结构&#xff08;如下图所示&#xff09;&#xff0c;共同完成对数据的存储和验证&#xff0c;确保交易的有效性。 一、区块…

常见的反爬手段和解决思路

常见的反爬手段和解决思路 学习目标 了解 服务器反爬的原因了解 服务器常反什么样的爬虫了解 反爬虫领域常见的一些概念了解 反爬的三个方向了解 常见基于身份识别进行反爬了解 常见基于爬虫行为进行反爬了解 常见基于数据加密进行反爬 1 服务器反爬的原因 爬虫占总PV(PV是指…

基于nodejs+vue的中国古诗词的设计与实现

目 录 摘要 I Abstract II 1 绪论 1 1.1 选题背景 1 1.2 选题意义 1 1.3 研究内容 2 2 相关技术介绍 3 3 系统分析 5 3.1可行性分析 5 3.1.1 操作可行性 5 3.1.2 经济可行性 5 3.1.3 技术可行性 5 3.2 需求分析 5 3.2.1非功能性需求 …

Python学习——(数据类型及其常用函数)

目录 一、数据类型 判断数据类型type() 二、数据类型的转换 三、运算符 (一)算数运算符 (二)赋值运算符 (三)复合赋值运算符 (四)比较运算符 (五)逻辑运算符 四、输入输出 (一)输出 (二)输入 五、各数据类型常用函数 (一)数值函数 1.绝对值abs(x) 2.最大值max(…

Git + Jenkins 自动化 NGINX 发布简易实现

概述 之前基于 GitLab Jenkins 实现了简单的 NGINX 的自动化发布。 具体包含如下的组件&#xff1a; GitLab包括 GItLab 的 WebHook&#xff1b; Jenkins 及其插件&#xff1a;Generic Webhook TriggerPublish Over SSH &#x1f9e0;疑问&#xff1a; 为什么不用 Ansible&…

算法训练营DAY44|518. 零钱兑换 II、377. 组合总和 Ⅳ

这两道题是对于完全背包题型的另一个维度&#xff0c;都是求解给定背包容量求装满背包最多有几种方法的题目。两道题十分相像&#xff0c;但在遍历顺序上却又有着极其微妙的差别。 518. 零钱兑换 II - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/coin…

看完流浪地球2,这些功课你必须要补

昨天看了流浪地球2&#xff0c;里面的信息量太大了&#xff0c;回来补充了一些功课&#xff0c;这些知识&#xff0c;看完&#xff0c;才算对流浪地球2有了进一步了解。地球人的航天路径我们的太空航程是什么样子的呢&#xff1f;要从第二次世界大战开始。回形针计划&#xff1…

金仓数据库简单巡检与事务阻塞会话简单处理

查看KES版本信息 使用version函数查看 这种方式能详细显示信息 使用ksql查看 查看license有效期 查看实例启动时间和运行时长 查看实例启动时间 查看KES无故障运行时长 查看数据库列表 使用元命令\l查看 使用字典查看 查看数据库占用的磁盘空间 统计当前数据库所占的磁盘空…

chrome在爬虫中的使用

chrome浏览器使用方法介绍 学习目标 了解 新建隐身窗口的目的了解 chrome中network的使用了解 寻找登录接口的方法 1 新建隐身窗口 浏览器中直接打开网站&#xff0c;会自动带上之前网站时保存的cookie&#xff0c;但是在爬虫中首次获取页面是没有携带cookie的&#xff0c;这…

Wireshark TS | Packet Challenge 之 FTP 案例分析

前言 来自于 Sharkfest Packet Challenge 中的一个数据包案例&#xff0c;Sharkfest 是 Wireshark 官方组织的一年一度的大会&#xff0c;致力于在 Wireshark 开发人员和用户社区之间分享知识、经验和最佳实践。印象中早期是一年一次&#xff0c;近几年发展成一年两次&#xf…

【华为上机真题】工号不够用咋办

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

2D测量 Metrology——

基本流程: 1,创建计量模型 create_metrology_model (MetrologyHandle) 2,设置计量模型的图像大小 set_metrology_model_image_size (MetrologyHandle, Width, Height) 3,将计量对象添加至计量模型中.(矩形,直线,圆,椭圆) add_metrology_object_rectangle2_measure  //矩形…

【高并发】- 指标介绍

什么是高并发 高并发系统有哪些关键指标 高并发系统介绍 本文主要讲解高并发系统的概念&#xff0c;在实际开发过程中为什么要使用高并发系统&#xff0c;相比于传统系统&#xff0c;能带来怎样的改变。 1.1 高并发介绍 高并发&#xff08;Hign Concurrency&#xff09;&#…

Linux常用命令——sed命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) sed 功能强大的流式文本编辑器 补充说明 sed是一种流编辑器&#xff0c;它是文本处理中非常中的工具&#xff0c;能够完美的配合正则表达式使用&#xff0c;功能不同凡响。处理时&#xff0c;把当前处理的行存…

剖析“类和对象” (下) -------- CPP

学习完“类和对象”(上)【剖析“类和对象” (上) -------- CPP】和(中)【剖析“类和对象” (中) -------- CPP】&#xff0c;相信各位同学对CPP中类与对象的理解或多或少都加深了一点。 本篇博客将和大家一同再次学习CPP中类和对象的知识点&#xff0c;跟随本篇博客的脚步定能…

Java两大工具库:Commons和Guava(1)

您好&#xff0c;我是湘王&#xff0c;这是我的CSDN博客。值此新春佳节&#xff0c;我给您拜年啦&#xff5e;祝您在新的一年中所求皆所愿&#xff0c;所行皆坦途&#xff0c;展宏“兔”&#xff0c;有钱“兔”&#xff0c;多喜乐&#xff0c;常安宁&#xff01;Java的成功很大…

Kettle(8):删除组件

删除组件能够按照指定条件,将表中的数据删除。 1 需求 有以下一个文本文件,文本文件包含了要删除的两个用户id: id 392456197008193000 267456198006210000 需要使用Kettle将文本文件中两个ID对应的t_user1表的数据删除。 2 构建Kettle数据流图 效果图: 1 将文本文件输…