Android ViewModel、LiveData 使用详解

一、ViewModel

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

1.1 简单实现

    class MyViewModel : ViewModel() {
        val users = MutableLiveData<List<User>>()

        fun loadUsers() {
            // 执行异步操作以获取用户列表
            users.value = userDao.getUsers()
        }
    }
    

注意:ViewModel 绝不能引用视图、Lifecycle 或可能存储对 Activity 上下文的引用的任何类。

可以从 Activity 访问该列表,如下所示:

    class MyActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
        	// Use the 'by viewModels()' Kotlin property delegate
            // from the activity-ktx artifact
            val viewModel: MyViewModel by viewModels()
            //val viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
            
            viewModel.loadUsers()
            viewModel.users.observe(this, Observer<List<User>>{ users ->
                // update UI
            })
        }
    }

部分代码用到了Android KTX

1.2 ViewModel 的生命周期

ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProviderLifecycle。ViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失:对于 Activity,是在 Activity 完成时;而对于 Fragment,是在 Fragment 分离时。
在这里插入图片描述
通常在系统首次调用 Activity 对象的 onCreate() 方法时请求 ViewModel。系统可能会在 Activity 的整个生命周期内多次调用 onCreate(),如在旋转设备屏幕时。ViewModel 存在的时间范围是从您首次请求 ViewModel 直到 Activity 完成并销毁。

1.3 在 Fragment 之间共享数据

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。我们可以利用ViewModel实现Activity 中多个Fragment 共享数据。这多个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信,如以下示例代码所示:

    class SharedViewModel : ViewModel() {
        val share:String = ""
    }
    class MasterFragment : Fragment() {
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            
            // Use the 'by activityViewModels()' Kotlin property delegate
        	// from the fragment-ktx artifact
            val viewModel: SharedViewModel by activityViewModels()
            //val viewModel= ViewModelProvider(requireActivity()).get(SharedViewModel ::class.java)

            val btn: Button = findViewById(R.id.button)
            btn.setOnClickListener {
                // Update the UI
                viewModel.share.value = "share data"
            }
        }
    }
    class DetailFragment : Fragment() {

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            
            // Use the 'by activityViewModels()' Kotlin property delegate
        	// from the fragment-ktx artifact
            val viewModel: SharedViewModel by activityViewModels()
            //val viewModel= ViewModelProvider(requireActivity()).get(SharedViewModel ::class.java)
            
            val textView: TextView = findViewById(R.id.textView)
            textView.text = viewModel.share
        }
    }

这两个 Fragment 通过by activityViewModels()或者ViewModelProvider(requireActivity()).get(SharedViewModel ::class.java)的方式获取SharedViewModel实例的时候传入的都是依赖的同一个Activity,所以这两个 Fragment 会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。

总结:在获取范围限定相同的 ViewModel,就会收到相同的ViewModel实例。
这个也能实现同一导航图内共享ViewModel ,可参阅Android Navigation 组件(进阶篇)

1.4 与Kotlin 协程使用

关于协程的介绍可参阅:Kotlin协程详解
文中介绍的内置协程范围包含在viewmodel架构组件的 KTX 扩展程序中。请务必在使用时添加相应的依赖项:

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'

可以通过 ViewModel 的 viewModelScope 属性访问 ViewModel 的 CoroutineScope,如以下示例所示:

class MyViewModel: ViewModel() {
        init {
            viewModelScope.launch {
                // Coroutine that will be canceled when the ViewModel is cleared.
            }
        }
    }

如果 ViewModel 已清除,则在此范围内启动的协程都会自动取消。

二、LiveData

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

如果观察者(由 Observer 类表示)的生命周期处于 STARTEDRESUMED 状态,则 LiveData 会认为该观察者处于活跃状态。LiveData 只会将更新通知给活跃的观察者。为观察 LiveData 对象而注册的非活跃观察者不会收到更改通知。

在 Activity 和 Fragment 特别有用,因为它们可以放心地观察 LiveData 对象而不必担心泄露(当 Activity 和 Fragment 的生命周期被销毁时,系统会立即退订它们)。

LiveDataMutableLiveData的其实在概念上是一模一样的。唯一几个的区别如下:

  1. MutableLiveData的父类是LiveData
  2. LiveData在实体类里可以通知指定某个字段的数据更新.
  3. MutableLiveData则是完全是整个实体类或者数据类型变化后才通知。不会细节到某个字段

2.1 简单实现

LiveData 是一种可用于任何数据的封装容器,其中包括可实现 Collections 的对象,如 List。LiveData 对象通常存储在 ViewModel 对象中,并可通过 getter 方法进行访问,如以下示例中所示:

    class NameViewModel : ViewModel() {

        // Create a LiveData with a String
        val currentName: MutableLiveData<String> by lazy {
            MutableLiveData<String>()
        }

        // Rest of the ViewModel...
    }
    

在大多数情况下,我们是在 onCreate() 方法给 LiveData 对象添加观察者

    class NameActivity : AppCompatActivity() {

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        private val viewModel: NameViewModel by viewModels()

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val textView: TextView = findViewById(R.id.textView)
            
            viewModel.currentName.observe(this,  Observer<String> { newName ->
                // Update the UI, in this case, a TextView.
                textView.text = newName
            })
        }
    }
    

我们可以通过setValue(T)postValue(T) 方法,修改存储在 LiveData 对象中的值并通知观察者。

button.setOnClickListener {
        val anotherName = "John Doe"
        viewModel.currentName.value = anotherName 
    }

区别:

  • setValue():只能在主线程中调用
  • postValue():可以在任何线程中调用

2.2 与Room 使用

Room 是官方推荐使用的数据库,了解详情可参阅:Android数据库框架——Room框架的使用
Room 数据库支持返回 LiveData 对象的可观察查询。可观察查询属于数据库访问对象 (DAO) 的一部分。我们可以通过查询操作直接返回LiveData类型数据

	@Query("select * from user where userId = :id")
	fun getUserById(id: Long): LiveData<User>

2.2 与Kotlin 协程使用

关于协程的介绍可参阅:Kotlin协程详解
文中介绍的内置协程范围包含在LiveData 架构组件的 KTX 扩展程序中。请务必在使用时添加相应的依赖项:

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'

在以下示例中,loadUser() 是在其他地方声明的 suspend 函数。 您可以使用 liveData 构建器函数异步调用 loadUser(),然后使用 emit() 来发出结果:

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}

当 LiveData 变为活动状态时,代码块开始执行;当 LiveData 变为非活动状态时,代码块会在可配置的超时过后自动取消。如果代码块在完成前取消,则会在 LiveData 再次变为活动状态后重启;如果在上次运行中成功完成,则不会重启。注意:代码块只有在自动取消的情况下才会重启。如果代码块由于任何其他原因(例如,抛出 CancelationException)而取消,则不会重启。

您还可以从代码块中发出多个值。每次 emit() 调用都会暂停执行代码块,直到在主线程上设置 LiveData 值。

    val number: LiveData<String> = liveData {
    	delay(1000)
        emit("1")
        delay(1000)
        emit("2")
        delay(1000)
        emit("3")
        delay(1000)
        emit("4")
        delay(1000)
        emit("5")
    }
    class NameActivity : AppCompatActivity() {

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        private val viewModel: NameViewModel by viewModels()

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val textView: TextView = findViewById(R.id.textView)
            
            viewModel.currentName.observe(this, Observer {
            textview.text = it
        	})
        }
    }
    

就能看到如下效果:
在这里插入图片描述
每次调用 emit()emitSource() 都会移除之前添加的来源。


版权声明:本文为weixin_42046829原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。