写点什么

学会使用 LiveData 和 ViewModel,我相信会让你在写业务时变得轻松🌞

  • 2022 年 7 月 01 日
  • 本文字数:7983 字

    阅读完需:约 26 分钟

学会使用LiveData和ViewModel,我相信会让你在写业务时变得轻松🌞

前言:

本文不定时更新,有问题欢迎在评论区提出~

最近更新时间:2022-06-21

介绍

在 2017 年,那时,观察者模式有效的简化了开发,但是诸如 RxJava 一类的库有一些太过复杂,学习成本太高,为此,LiveData 出现了,一个专用于 Android 的,具备自主生命周期感知能力的,可观测的数据存储类。同时也出现了 ViewModel 这个组件,配合 LiveData,更方便的实现 MVVM 模式中 Model 与 View 的分离。那么就让本文来带大家来学习 LiveData 与 ViewModel 的使用吧。

LiveData 和 ViewModel 的关系:


本文的案例代码:https://github.com/taxze6/Jetpack_learn/tree/main/Jetpack_basic_learn/livedata_viewmodel

LiveData

参考资料:


🌟官方文档:https://developer.android.google.cn/topic/libraries/architecture/livedata


🌟LiveData postValue 详解:https://www.cnblogs.com/button123/p/14871526.html


LiveData 是一种可观察的数据存储器类(响应式编程,类似 Vue)。与常规的可观察类不同,LiveData 具有生命周期感知能力。LiveData 最重要的是它了解观察者的生命周期,如 Activity Fragment。


因此,当 LiveData 发送变化时,UI 会收到通知,然后 UI 可以使用新数据重新绘制自己。换句话说,LiveData 可以很容易地使屏幕上发生的事情与数据保持同步(响应式编程的核心)

使用 LiveData 具有以下优势:

  • UI 与数据状态匹配

  • LiveData 遵循观察者模式。当底层数据发生变化时,LiveData 会通知Observer对象。您可以整合代码以在这些 Observer对象中更新界面。这样一来,您无需在每次应用数据发生变化时更新界面,因为观察者会替您完成更新。

  • 提高代码的稳定性

  • 代码稳定性在整个应用程序生命周期中增加:

  • 活动停止时不会发生崩溃。如果应用程序组件处于非活动状态,则这些更改不受影响。因此,您在更新数据时无需担心应用程序组件的生命周期。对于后台堆栈中的活动,它不会接受任何 LiveData 事件

  • 内存泄漏会减少,观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理

  • 取消订阅任何观察者时无需担心

  • 如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。

  • 不再需要手动处理生命周期

  • 界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。

  • 数据始终保持最新状态

  • 如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

  • 共享资源

  • 像单例模式一样,我们也可以扩展我们的 LiveData 对象来包装系统服务,以便它们可以在我们的应用程序中共享。一旦 LiveData 对象连接到系统服务,任何需要资源的观察者可以轻松地观看 LiveData 对象。

在以下情况中,不要使用 LiveData:

  • 您需要在信息上使用大量运算符,尽管 LiveData 提供了诸如转换之类的工具,但只有 Map 和 switchMap 可以帮助您

  • 您没有与信息的 UI 交互

  • 您有一次性的异步操作

  • 您不必将缓存的信息保存到 UI 中

如何使用 LiveData

一般来说我们会在 ViewModel 中创建 Livedata 对象,保证 app 配置变更时,数据不会丢失,然后再 Activity/Fragment 的 onCreate 中注册 Livedata 监听(因为在 onStart 和 onResume 中进行监听可能会有冗余调用)

基础使用流程:

1.创建一个实例 LiveData 来保存某种类型的数据。一般在你创建的 ViewModel 类中完成


class MainViewModel : ViewModel() {    var mycount: MutableLiveData<Int> = MutableLiveData()}
复制代码


2.在 Activity 或者 Fragment 中获取到 ViewModel,通过 ViewModel 获取到对应的 LiveData


class MainActivity : AppCompatActivity() {    lateinit var viewModel: MainViewModel    override fun onCreate(savedInstanceState: Bundle?) {        ...        /**记住绝对不可以直接去创建ViewModel实例        一定要通过ViewModelProvider(ViewModelStoreOwner)构造函数来获取。        因为每次旋转屏幕都会重新调用onCreate()方法,如果每次都创建新的实例的话就无法保存数据了。        用上述方法后,onCreate方法被再次调用,        它会返回一个与MainActivity相关联的预先存在的ViewModel,这就是保存数据的原因。*/        viewModel = ViewModelProvider(this@MainActivity,ViewModelProvider.                        NewInstanceFactory()).get(MainViewModel::class.java)    }}
复制代码


3.给 LiveData 添加观察者监听,用来监听 LiveData 中的数据变化,在 Observer 的 onChanged 中使用监听回调数据


/** *  订阅 ViewModel,mycount是一个LiveData类型 可以观察 * */        viewModel.mycount.observe(this@MainActivity) {    countTv.text = viewModel.mycount.value.toString()}// LiveData onchange会自动感应生命周期 不需要手动//        viewModel.mycount.observe(this, object : Observer<Int> {//            override fun onChanged(t: Int?) {////            }//        })
复制代码

进阶用法:

Transformations.map


现在有一个场景:我们通过网络请求,获得了一个 User 类数据(LiveData),但是,我们只想把 User.name 暴露给外部观察者,这样我们就可以通过 Transformations.map 来转化 LiveData 的数据类型,从而来实现上述场景。这个函数常用于对数据的封装。


//实体类data class User(var name: String)...//Transformations.map接收两个参数,第一个参数是用于转换的LiveData原始对象,第二个参数是转换函数。private val userLiveData: MutableLiveData<User> = MutableLiveData()    val userNames: LiveData<String> = Transformations        .map(userLiveData) { user ->            user.name}
复制代码


Transformations.switchMap


switchMap 是根据传入的 LiveData 的值,然后判断这个值,然后再去切换或者构建新的 LiveData。比如我们有些数据需要依赖其他数据进行查询,就可以使用 switchMap。

例如,有一个学生,他有两门课程的成绩,但是在 UI 组件中,我们一次只能显示一门课的成绩,在这个需要判断展示哪门课程成绩的需求下,我们就可以使用 switchMap。


data class Student    (var englishScore: Double, var mathScore: Double, val scoreTAG: Boolean).....class SwitchMapViewModel:ViewModel {    var studentLiveData = MutableLiveData<Student>()    val transformationsLiveData = Transformations.switchMap(studentLiveData) {        if (it.scoreTAG) {            MutableLiveData(it.englishScore)        } else {            MutableLiveData(it.mathScore)        }    }}//使用时:var student = Student()person.englishScore = 88.2person.mathScore = 91.3//判断显示哪个成绩person.condition = trueswitchMapViewModel.conditionLiveData.postValue(person)
复制代码


MediatorLiveData


MediatorLiveData 继承于 MutableLiveData,在 MutableLiveData 的基础上,增加了合并多个 LiveData 数据源的功能。其实就是通过 addSource()这个方法去监听多个 LiveData。

例如:现在有一个存在本地的 dbLiveData,还有一个网络请求来的 LiveData,我们需要讲上面两个结果结合之后展示给用户,第一种做法是我们在 Activity 中分别注册这两个 LiveData 的观察者,当数据发生变化时去更新 UI,但是我们其实使用 MediatorLiveData 可以简化这个操作。


class MediatorLiveDataViewModel : ViewModel() {    var liveDataA = MutableLiveData<String>()    var liveDataB = MutableLiveData<String>()    var mediatorLiveData = MediatorLiveData<String>()        init {        mediatorLiveData.addSource(liveDataA) {            Log.d("This is livedataA", it)            mediatorLiveData.postValue(it)        }        mediatorLiveData.addSource(liveDataB) {            Log.d("This is livedataB", it)            mediatorLiveData.postValue(it)        }    }}
复制代码

解释:

如果是第一次接触到 LiveData 的朋友可能会发现,我们虽然一直在提 LiveData,但是用的时候却是 MutableLiveData,这两个有什么关系呢,既然都没怎么用 LiveData,那么把标题直接改成 MutableLiveData 吧其实,LiveData 与 MutableLiveData 在概念上是一模一样的。唯一的几个区别分别是:


💡“此处引用:LiveData与MutableLiveData的区别文章中的段落”

  • MutableLiveData 的父类是 LiveData

  • LiveData 在实体类里可以通知指定某个字段的数据更新

  • MutableLiveData 则是完全是整个实体类或者数据类型变化后才通知.不会细节到某个字段。

原理探究:

对于 LiveData 的基础使用我们就讲到这里,想要探索 LiveData 原理的朋友可以从下面几个角度:


  • LiveData 的工作原理

  • LiveData 的 observe 方法源码分析

  • LifecycleBoundObserver 源码分析

  • activeStateChanged 源码分析(用于粘性事件)

  • postValue 和 setValue

  • considerNotify 判断是否发送数据分析

  • 粘性事件的分析


相信大家从以上几个角度去分析 LiveData 会有不小的收获💪

ViewModel

官方文档:https://developer.android.google.cn/topic/libraries/architecture/viewmodel

官方简介

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

生命周期


ViewModel 的生命周期会比创建它的 Activity、Fragment 的生命周期都要长。所以 ViewModel 中的数据会一直存活在 Activity/Fragment 中。

基础使用流程:

1.构造数据对象


自定义 ViewModel 类,继承 ViewModel,然后在自定义的 ViewModel 类中添加需要的数据对象


class MainViewModel : ViewModel() {    ...}
复制代码


2.获取数据


有两种常见的 ViewModel 创建方式,第一种是在 activity 或 fragment 种直接基于 ViewModelProvider 获取。第二种是通过 ViewModelFactory 创建


//第一种 ViewModelProvider直接获取ViewModelProvider(this@MainActivity).get(MainViewModel::class.java)//第二种 通过 ViewModelFactory 创建class TestViewModelFactory(private val param: Int) : ViewModelProvider.Factory {    override fun <T : ViewModel> create(modelClass: Class<T>): T {        return TestViewModel(param) as T    }}ViewModelProvider(this@MainActivity,TestViewModelFactory(0)).get(TestViewModel::class.java)
复制代码


使用 ViewModel 就是这么简单🚢

ViewModel 常见的使用场景

  • 使用 ViewModel,在横竖屏切换后,Activity 重建,数据仍可以保存

  • 同一个 Activity 下,Fragment 之间的数据共享

  • 与 LiveData 配合实现代码的解耦

ViewModel 和 onSaveInstanceState 的区别

我相信大家一定知道 onSaveInstanceState,它也是用来保存 UI 状态的,你可以使用它保存你所想保存的东西,在 Activity 被杀死之前,它一般在 onStop 或者 onPause 之前触发。虽然 ViewModel 被设计为应用除了 onSaveInstanceState 的另一个选项,但是还是有一些明显的区别。由于资源限制,ViewModel 无法在进程关闭后继续存在,但 onSaveInstance 包含执行此任务。ViewModel 是存储数据的绝佳选择,而 onSaveInstanceState bundles 不是用于该目的的合适选项。


ViewModel 用于存储尽可能多的 UI 数据。因此,在配置更改期间不需要重新加载或重新生成该数据。


另一方面,如果该进程被框架关闭,onSaveInstanceState 应该存储回复 UI 状态所需的最少数据量。例如,可以将所有用户的数据存放在 ViewModel 中,而仅将用户的数据库 ID 存储在 onSaveInstanceState 中。


android onSaveInstanceState调用时机详细总结


onSaveInstanceState用法及源码分析

ViewModel 和 Context

ViewModel 不应该包含对 Activity,Fragment 或 context 的引用,此外,ViewModel 不应包含对 UI 控制器(如 View)的引用,因为这将创建对 Context 的间接引用。当您旋转 Activity 被销毁的屏幕时,您有一个 ViewModel 包含对已销毁 Activity 的引用,这就是内存泄漏。因此,如果需要使用上下文,则必须使用应用程序上下文 (AndroidViewModel)


LiveData 和 ViewModel 的基本用法我们已经介绍完了,现在用几个例子带大家来更好的使用它们

案例一:计数器 — 两个 Activity 共享一个 ViewModel

话不多说,先上效果图:



虽然这个案例是比较简单的,但是我相信可以帮助你更快的熟悉 LiveData 和 ViewModel


想要实现效果图的话需要从下面几步来写(只讲解核心代码,具体代码请自己查看仓库):


第一步:创建 ViewModel


import androidx.lifecycle.LiveDataimport androidx.lifecycle.MutableLiveDataimport androidx.lifecycle.ViewModelclass MainViewModel : ViewModel() {    private var _mycount: MutableLiveData<Int> = MutableLiveData()    //只暴露不可变的LiveData给外部    val mycount: LiveData<Int> get() = _mycount    init {        //初始化        _mycount.value = 0    }    /**     * mycount.value若为空就赋值为0,不为空则加一     * */    fun add() {        _mycount.value = _mycount.value?.plus(1)    }    /**     * mycount.value若为空就赋值为0,不为空则减一,可以为负数     * */    fun reduce() {        _mycount.value = _mycount.value?.minus(1)    }    /**     * 随机参数     * */    fun random() {        val random = (0..100).random()        _mycount.value = random    }    /**     * 清除数据     * */    fun clear() {        _mycount.value = 0    }}
复制代码


第二步:标记 ViewModel 的作用域


因为,我们是两个 Activity 共享一个 ViewModel,所以我们需要标记 ViewModel 的作用域


import androidx.lifecycle.*/** * 用于标记viewmodel的作用域 */@Retention(AnnotationRetention.RUNTIME)@Target(AnnotationTarget.FIELD)annotationclass VMScope(val scopeName: String) {}private val vMStores = HashMap<String, VMStore>()fun LifecycleOwner.injectViewModel() {    //根据作用域创建商店    this::class.java.declaredFields.forEach { field ->        field.getAnnotation(VMScope::class.java)?.also { scope ->            val element = scope.scopeName            var store: VMStore            if (vMStores.keys.contains(element)) {                store = vMStores[element]!!            } else {                store = VMStore()                vMStores[element] = store            }            val clazz = field.type as Class<ViewModel>            val vm = ViewModelProvider(store, ViewModelProvider.NewInstanceFactory()).get(clazz)            field.set(this, vm)        }    }}class VMStore : ViewModelStoreOwner {    private var vmStore: ViewModelStore? = null    override fun getViewModelStore(): ViewModelStore {        if (vmStore == null)            vmStore = ViewModelStore()        return vmStore!!    }}
复制代码


第三步:在 Activity 中使用(都是部分代码)


class MainActivity : AppCompatActivity() {    @VMScope("count") //设置作用域    lateinit var viewModel: MainViewModel    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        injectViewModel()        initEvent()    }    private fun initEvent() {        val cardReduce: CardView = findViewById(R.id.card_reduce)        .....        cardReduce.setOnClickListener {            //调用自定义ViewModel中的方法            viewModel.reduce()        }                .....                /**         *  订阅 ViewModel,mycount是一个LiveData类型 可以观察         * */        viewModel.mycount.observe(this@MainActivity) {            countTv.text = viewModel.mycount.value.toString()        }}    在第二个Activity中也是类似...
复制代码


这样就可以实现效果图啦🏀

案例二:同一个 Activity 下的两个 Fragment 共享一个 ViewModel

话不多说,先上效果图



这个效果就很简单了,在同一个 Activity 下,有两个 Fragment,这两个 Fragment 共享一个 ViewModel


这个案例主要是想带大家了解一下 ViewModel 在 Fragment 中的使用


第一步:依旧是创建 ViewModel


class BlankViewModel : ViewModel() {    private val numberLiveData = MutableLiveData<Int>()    private var i = 0    fun getLiveData(): LiveData<Int> {        return numberLiveData    }    fun addOne(){        i++        numberLiveData.value = i    }}
复制代码


非常简单的一个 ViewModel


第二步:在 Fragment 中使用


//左Fragmentclass LeftFragment : Fragment() {        private val viewModel:BlankViewModel by activityViewModels()    override fun onCreateView(        inflater: LayoutInflater, container: ViewGroup?,        savedInstanceState: Bundle?    ): View? {        return inflater.inflate(R.layout.fragment_left, container, false)    }    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        //对+1按钮监听        left_button.setOnClickListener {            viewModel.addOne()        }        activity?.let {it ->            viewModel.getLiveData().observe(it){                left_text.text = it.toString()            }        }    }}//右Fragmentclass RightFragment : Fragment() {    private val viewModel: BlankViewModel by activityViewModels()        override fun onCreateView(        inflater: LayoutInflater, container: ViewGroup?,        savedInstanceState: Bundle?    ): View? {        return inflater.inflate(R.layout.fragment_right, container, false)    }    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        right_button.setOnClickListener {            viewModel.addOne()        }        activity?.let { it ->            viewModel.getLiveData().observe(it) {                right_text.text = it.toString()            }        }    }}
复制代码


这样,这个简单的案例就实现啦。

尾述

终于把 LiveData 和 ViewModel 的大致使用讲解了一遍,但仅仅这样还是不够的,你还需要在更多更多的实践中去熟悉,去深入学习....

关于我

Hello,我是 Taxze,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的博客


如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?😝


基础系列:


2022 · 让我带你Jetpack架构组件从入门到精通 — Lifecycle


学会使用 LiveData 和 ViewModel,我相信会让你在写业务时变得轻松🌞 (本文🌟)


以下部分还在码字,赶紧点个收藏吧🔥


2022 · 让我带你 Jetpack 架构组件从入门到精通 — DataBinding


2022 · 让我带你 Jetpack 架构组件从入门到精通 — Navigation


2022 · 让我带你 Jetpack 架构组件从入门到精通 — Room


2022 · 让我带你 Jetpack 架构组件从入门到精通 — Paging3


2022 · 让我带你 Jetpack 架构组件从入门到精通 — WorkManager


2022 · 让我带你 Jetpack 架构组件从入门到精通 — ViewPager2


2022 · 让我带你 Jetpack 架构组件从入门到精通 — 登录注册页面实战(MVVM)


进阶系列:


协程 + Retrofit 网络请求状态封装


Room 缓存封装


.....

发布于: 刚刚阅读数: 3
用户头像

他日若遂凌云志 敢笑黄巢不丈夫 2020.10.15 加入

曾许少年凌云志,誓做人间第一流. 一起加入Flutter技术交流群532403442 有好多好多滴学习资料喔~ 小T目前主攻Android与Flutter, 通常会搞搞人工智能、SpringBoot 、Mybatiys等.

评论

发布
暂无评论
学会使用LiveData和ViewModel,我相信会让你在写业务时变得轻松🌞_JetPack_编程的平行世界_InfoQ写作社区