LiveData、ViewModel
ViewModel
优点
ViewModel 被设计出来,不仅为了解决上面所说的 configuration 改变时候能保留数据。其真正意义在于以下几个方面:
- 职责分离:使 Activity/Fragment 不用再负责从某些数据源获取数据,只需要负责展示数据就好,同时还消除了在配置更改时保留数据对象实例的引用的责任。这两个职责都转给了 ViewModel。
- 简化对没用数据的清理:当 Activity/Fragment 负责清理数据的操作时,需要使用大量代码来清理这些请求。但是将这些清理操作放到 ViewModels onCleared ()方法中,这些资源在 Activity 结束时会自动清除。
- 容易测试:职责的分离会使测试这些职责更容易,而且还可以产生更细粒度的测试用例。
- 在 activity 销毁的时候,可以在 viewmodel 里统一做一些清理工作
- 状态保存,ViewModel 可以保存数据的状态,即使界面被销毁,数据也不会丢失。这使得开发者能够在不同的界面之间共享数据,并提供更好的用户体验。
- 跨组件共享数据,ViewModel 可以跨 Activity 或 Fragment 共享数据,这使得开发者能够在不同的界面之间共享数据,并避免数据重复加载。
原理
- ViewModelProviders 这个类,本质上其实是一个工厂类。这个类内部包含了一个 ViewModelStore 实例,它负责存储创建的 ViewModels。ViewModel 存储在 ViewModelStore 的 HasHmap 内存中。不做持久化数据存储,当 activity fragment 处于后台因内存问题被系统杀死后,重新进入后数据不会被恢复。
- AndroidViewModelFactory 的 create,从 ViewModelStore 取出 class,反射创建
- 可以使用 ViewModelProvider 的 get()方法来获取作为参数传入的 ViewModel 类型的实例。
ViewModelProvider 传 activity 的原因是
没有绑定 activity 的生命周期。
- 每个 activity 都有一个 ViewModelStore,ViewModelStore 里 HashMap<String, ViewModel> mMap,key 是自己 viewmodel 的名字,值是自己 viewmodel 的 class 类。
- viewModelProvider. Factory 创建 viewmodel 工厂
clear 什么时候执行
- ViewModelStore 是 HolderFragment 类的成员,并且在 HolderFragment 的 onDestroy () 调用了 clear (),遍历调用了它的 ViewModel map 里面的所有 ViewModel 的 clear();
- 在 ComponentActivity 创建的时候,ViewModelStore 进行了生命周期的绑定工作
1 |
|
ViewModelStore 源码
1 |
|
没有持有 view 的引用,要不然用 livedata 干啥
ViewModel 不应该直接引用 Views 的原因; 它们可以比 View 的生命周期更长久
ViewModel 不能持有 activity,fragment 等 view 的引用,避免内存泄漏。他可以获取到 application 的 context
坑
- 生命周期问题: 在 Activity 或 Fragment 的生命周期中,可能会出现 ViewModel 在不需要时仍然持有数据的情况。这可能导致内存泄漏因为 ViewModel 不会在不需要时自动释放资源。为了避免这种情况,请确保在合适的时机清除数据,例如在 onDestroy 方法中清除数据。
- 状态保存和恢复: 在使用 ViewModel 时,需要注意保存和恢复数据的状态。如果数据丢失,可能会导致用户界面出现问题。因此,需要适当地使用 ViewModel 来保存和恢复数据。
- 多个 Activity 或 Fragment 之间的数据共享: 当需要在多个 Activity 或 Fragment 之间共享数据时,使用 ViewModel 可以方便地实现这目标。但是,需要注意传递数据的正确性,避免出现数据不一致的情况。
数据恢复
在组件短暂被销毁的时候可以保留数据
SavedStateHandle 是一个类,它用于保存 ViewModel 的状态数据。SavedStateHandle 可以保存任何可序列化类型的数据,例如基本类型、String、List、Map 等。
具体实现流程如下:
- 当 ViewModel 实例创建时,ViewModelProvider 会创建一个 SavedStateHandle 实例并将其传递给 ViewModel 实例。
- ViewModel 实例可以将需要保存的数据存储在 SavedStateHandle 中。
- 当 Activity 或 Fragment 被销毁时,ViewModelProvider 会将 SavedStateHandle 实例保存到 Bundle 中。
- 当 Activity 或 Fragment 重新创建时,ViewModelProvider 会从 Bundle 中恢复 SavedStateHandle 实例,并将其传递给新的 ViewModel 实例。
- 新的 ViewModel 实例可以从 SavedStateHandle 中获取之前保存的数据。
屏幕旋转,acitivity 会重建数据丢失,导致闪退。保存在 viewmodel 里的数据会自动进行恢复(例如,用户旋转屏幕)
1 |
|
需要开发者手动调用 savedStateHandle.set()
方法进行保存。
原因如下:
- 灵活性: 自动保存所有数据可能会导致不必要的开销,因为并非所有数据都需要持久保存。例如,一些临时数据可能只在当前界面中使用,不需要保存到 Bundle 中。
- 控制力: 手动保存数据可以让开发者更好地控制保存哪些数据以及何时保存数据。例如,开发者可以选择在特定条件下才保存数据,或者只保存部分数据。
ViewModel 还提供了以下方法来帮助开发者保存状态:
- **onSaveInstanceState ()**:该方法用于保存 ViewModel 的临时状态数据。
- **onRestoreInstanceState ()**:该方法用于恢复 ViewModel 的临时状态数据。
一般并存
怎么保存生命周期
ViewModel2.0之前呢,其实原理是在 Activity 上 add 一个 HolderFragment,然后设置 setRetainInstance (true)方法就能让这个 Fragment 在 Activity 重建时存活下来,也就保证了 ViewModel 的状态不会随 Activity 的状态所改变。
2.0之后,其实是用到了 Activity 的 onRetainNonConfigurationInstance ()和 getLastNonConfigurationInstance ()这两个方法,相当于在横竖屏切的时候会保存 ViewModel 的实例,然后恢复,所以也就保证了 ViewModel 的数据。
LiveData
一句话概括 LiveData:LiveData 是可感知生命周期的,可观察的,数据持有者。
它的能力和作用很简单:更新 UI。
它有一些可以被认为是优点的特性:
- 观察者的回调永远发生在主线程 也是缺点
- 仅持有单个且最新的数据
- 自动取消订阅
- 提供「可读可写」和「仅可读」两个版本收缩权限
- 配合 DataBinding 实现「双向绑定」
不做跟风党,LiveData,StateFlow,SharedFlow 使用场景对比 - 掘金
持有一份给定的数据(不一定是 viewmodel 的,可以是任意数据,room,自己的 list 等等),并且能够在生命周期变化中观察它。LiveData 会根据观察者绑定的 LifecycleOwner 的生命周期情况,来决定是否将数据改变的情况通知给观察者。
setValue 需要在主线程中调用,如果在子线程中,那么需要使用 postValue。(Handler)
监听 Acitivty 生命周期可以这样写
实现:DefaultLifecycleObserver
注册:getLifecycle (). addObserver (GpsManager. getInstance)
LiveData 是一个可被观察的数据持有者类,它只*有在 STARTED 或者 RESUMED 状态时才会被激活,在 DESTROYED 状态时,会自动 removeObserver ()*,在这也可以看出,它和普通的 Observer 不一样,它对生命周期是有感知能力的。有一种情况下,不会自动 removeObserver ():当你调用 observeForever ()方法的时候,你需要手动去调用 removeObserver()方法。
LiveData 源码
https://juejin.cn/post/6844903748574117901
有同学提出,我如果希望这种情况下,Activity 在后台依然能够响应数据的变更,可不可以呢?当然可以,LiveData 此外还提供了 observerForever ()方法,在这种情况下,它能够响应到任何生命周期中数据的变更事件
setValue 里会执行 Observer 的 onChanged 方法
1 |
|
LiveData 的优点
- UI 和实时数据保持一致,因为 LiveData 采用的是观察者模式,这样一来就可以在数据发生改变时获得通知,更新 UI。
- 避免内存泄漏,观察者被绑定到组件的生命周期上,当被绑定的组件销毁(destroy)时,观察者会立刻自动清理自身的数据。
- 不会再产生由于 Activity 处于 stop 状态而引起的崩溃,例如:当 Activity 处于后台状态时,是不会收到 LiveData 的任何事件的。当某个页面请求网络数据成功后需要同步 UI, 但这个页面已经不可见, 这时就会停止同步 UI 的操作
- 不需要再解决生命周期带来的问题,LiveData 可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。
- 实时数据刷新,当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据。
- 解决 Configuration Change 问题,在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。
- LiveData 能够自动解除订阅而避免内存泄漏了,因为它内部能够感应到 Activity 或者 Fragment 的生命周期。通过 LifecycleOwner
一条消息能被多个观察者消费,多个页面公用 liveDta, 实现多个页面数据的同时监听
1 |
|
需要生命周期的工具类,更加内聚
LiveData 的缺点
- LiveData 只能在主线程更新数据: 只能在主线程 setValue,即使 postValue 内部也是切换到主线程执行
- LiveData 不防抖: 重复 setValue 相同的值,订阅者会收到多次 onChanged () 回调(可以使用 distinctUntilChanged () 解决,此处不展开);
- LiveData 不支持背压: 在数据生产速度 > 数据消费速度时,LiveData 无法正常处理。比如在子线程大量 postValue 数据但主线程消费跟不上时,中间就会有一部分数据被忽略。
- LiveData 数据重放问题: 注册新的订阅者,会重新收到 LiveData 存储的数据,这在有些情况下不符合预期(可以使用自定义的 LiveData 子类 SingleLiveData 或 UnPeekLiveData 解决)
不防抖
setValue()/postValue()
传入相同的值多次调用,观察者的 onChanged()
会被多次调用。
严格讲这不算一个问题,看具体的业务场景,处理也很容易,官方在 Transformations
中提供了 distinctUntilChanged()
方法,配合官方提供的扩展函数,如下使用即可:
1 |
|
粘性事件
如何优雅的使用LiveData实现一套EventBus(事件总线) - 简书
设计成了黏性事件,发送事件后在订阅也可以收到消息。
原因:
Android 使用 LiveData 实现 EventBus - 掘金
1 |
|
记住这里的 mVersion,它本问题关键,每次更新数据都会自增,默认值是 -1。然后我们跟进下 dispatchingValue () 方法:
1 |
|
可以看到,无论条件判断,最终都会执行 considerNotify () 方法,所以我们继续跟进:
1 |
|
终于到了最关键的时候了!!如果 ObserverWrapper 的 mLastVersion 小于 LiveData 的 mVersion,那么就会执行的 onChange () 方法去通知观察者数据已更新。
而 ObserverWrapper. mLastVersion 的默认值是 -1, LiveData 只要更新过数据,mVersion 就肯定会大于 -1,所以订阅者会马上收到订阅之前发布的最新消息!!
只接收一次
如果在多线程中同一个时刻,多次调用了 postValue () 方法,==只有最后次调用的值会得到更新==。也就是此方法是有可能会丢失事件!!
为什么这么设计
一种理解是为了兼顾性能,UI 只需显示最终状态即可,省略中间态造成的频发刷新。这或许是设计目的之一,但是一个更为合理的解释是:即使 post 多次也没有意义,所以只 post 一次即可
1 |
|
从上面的源码就可以很容易看出,postValue 只是把传进来的数据先存到 mPendingData,然后往主线程抛一个 Runnable,在这个 Runnable 里面再调用 setValue 来把存起来的值真正设置上去,并回调观察者们。而如果在这个 Runnable 执行前多次 postValue,其实只是改变暂存的值 mPendingData,并不会再次抛另一个 Runnable。
重写 postValue 方法
解决
解决这个问题的方案有多种,其中美团大佬Android 消息总线的演进之路:用 LiveDataBus 替代 RxBus、EventBus 使用的反射的方式修改 LiveData 中的 mVersion 值去实现。还有另一个方案基于 LiveData 实现事件总线思路和方案,此方案是基于自定义观察者包装类,因为粘性消息最终会调用到 Observer onChange () 方法,因此我们自定义 Observer 包装类,自己维护实际的订阅消息数,来判断是否需要触发真正的 onChange() 方法。
包装类
1 |
|
这里我们保存了 LiveData 的 mVersion 值,每次执行 onChange () 时都先判断一些 LiveData 是否更新过数据,如果没有则不执行观察者的 Observer.onChange () 方法。
反射
美团是根据覆写 observe 方法, 反射获取ObserverWrapper. mLastVersion
, 在订阅的时候使得初始化的ObserverWrapper. mLastVersion
等于LiveData. mVersion
, 使得粘性消息无法通过实现
1 |
|
LifecycleOwner
Lifecycle 是一个接口,是一个生命周期感知组件能够感知 Activity、 Fragment 等组件的生命周期变化,并将变化通知到已注册的观察者
它拥有 Activity、Fragment 所有生命周期的方法,在实现了这个接口的 Activity、Fragment 对象中,每个生命周期对应的方法都会被回调,LifecycleOwner 之所以设计成接口,是为了其它对象可以使用到,这样其它对象就无需要求 Activity、Fragment 在特定的生命周期中调用特定的方法,比如终结方法、暂停方法,而这些要求往往可能被程序员所忽略,也使 Activity、Fragment 变得臃肿复杂。
业务层逻辑更加内聚,无需依赖 UI 去做生命周期相关阶段的处理,避免出错
真正有生命周期的是 lifrcycle
1 |
|
原理:接口,在 Activity、Fragment 生命周期的方法里调用 Lifecycle 接口各自的方法
LifecycleOwner 观察它
LifecycleObserver 观察者
可以通过被 LifecycleOwner 类的 addObserver (LifecycleObserver o)方法注册, 被注册后,LifecycleObserver 便可以观察到 LifecycleOwner 的生命周期事件。
当一个应用程序实现了 Application. ActivityLifecycleCallbacks 接口时,它可以注册一个监听器来监控和响应应用程序中活动(Activity)的生命周期事件。通过实现这个接口,应用程序可以接收有关活动的创建、启动、暂停、恢复和销毁等生命周期事件的通知。
看前后台:implements Application.ActivityLifecycleCallbacks
VoiceViewManager DefaultLifecycleObserver
使用前需要在当前页面注册监听 lifecycle.addObserver (VoiceViewManager)
其他
有些时候我们从 repository 层拿到的数据需要进行处理,例如从数据库获得 User List,我们想根据 id 获取某个 User。
此时我们可以借助 MediatorLiveData 和 Transformatoins 来实现:
1 |
|