🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # Lifecycle ## 解决问题: * activity的生命周期内有大量管理组件的代码,难以维护。 * 无法保证组件会在 Activity/Fragment停止后不执行启动 ## 实现 LifecycleOwner(如Activity)在生命周期状态改变时(也就是生命周期方法执行时),遍历观察者,获取每个观察者的方法上的注解。 # LifeData **LiveData使得 数据的更新 能以观察者模式 被observer感知,且此感知只发生在 LifecycleOwner的活跃生命周期状态**。 ## 基本用法 1. 创建LiveData实例,指定源数据类型 2. 创建Observer实例,实现onChanged()方法,用于接收源数据变化并刷新UI 3. LiveData实例使用observe()方法添加观察者,并传入LifecycleOwner 4. LiveData实例使用setValue()/postValue()更新源数据 (子线程要postValue()) ## LiveData与MutableLiveData区别 1. MutableLiveData的父类是LiveData 2. LiveData在实体类里可以通知指定某个字段的数据更新. 3. MutableLiveData则是完全是整个实体类或者数据类型变化后才通知.不会细节到某个字段 4. LiveData不可变,MutableLiveData是可变的 ## 源码分析(观察者模式) LiveData原理是观察者模式,下面就先从LiveData.observe()方法看起: ~~~ //key 观察者 value 包装的LifecycleOwner private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = new SafeIterableMap<>(); ~~~ ## 事件推送,解决防止事件丢失 投递事件时,如果判断Lifecycle是否激活,如果NO,则把数据暂存起来 ## 解决数据倒灌 对于这个问题,总结一下发生的核心原因。对于LiveData,其初始的version是-1,当我们调用了其setValue或者postValue,其vesion会+1;对于每一个观察者的封装ObserverWrapper,其初始version也为-1,也就是说,每一个新注册的观察者,其version为-1;当LiveData设置这个ObserverWrapper的时候,如果LiveData的version大于ObserverWrapper的version,LiveData就会强制把当前value推送给Observer。 那么能不能从**Map容器mObservers中取到LifecycleBoundObserver,然后再更改version呢**?答案是肯定的,通过查看SafeIterableMap的源码我们发现有一个protected的get方法。因此,在调用observe的时候,我们可以通过反射拿到LifecycleBoundObserver,再把LifecycleBoundObserver的version设置成和LiveData一致即可。 # ViewModel ## 特点 ### 生命周期长于Activity ViewModel最重要的特点是 生命周期长于Activity。来看下官网的一张图: 看到在因屏幕旋转而重新创建Activity后,ViewModel对象依然会保留。 只有Activity真正Finish的时ViewModel才会被清除。 也就是说,因系统配置变更Activity销毁重建,ViewModel对象会保留并关联到新的Activity。而Activity的正常销毁(系统不会重建Activity)时,ViewModel对象是会清除的。 那么很自然的,因系统配置变更Activity销毁重建,ViewModel内部存储的数据 就可供重新创建的Activity实例使用了。这就解决了第一个问题。 ### 不持有UI层引用 我们知道,在MVP的Presenter中需要持有IView接口来回调结果给界面。 而ViewModel是不需要持有UI层引用的,那结果怎么给到UI层呢?答案就是使用上一篇中介绍的基于观察者模式的LiveData。 并且,ViewModel也不能持有UI层引用,因为ViewModel的生命周期更长。 所以,ViewModel不需要也不能 持有UI层引用,那么就避免了可能的内存泄漏,同时实现了解耦。这就解决了第二个问题。 ## 原理 ### ViewModelStore ``` ** * 用于存储ViewModels. * ViewModelStore实例 必须要能 在系统配置改变后 依然存在。 */ public class ViewModelStore { private final HashMap<String, ViewModel> mMap = new HashMap<>(); final void put(String key, ViewModel viewModel) { ViewModel oldViewModel = mMap.put(key, viewModel); if (oldViewModel != null) { oldViewModel.onCleared(); } } final ViewModel get(String key) { return mMap.get(key); } Set<String> keys() { return new HashSet<>(mMap.keySet()); } /** * 调用ViewModel的clear()方法,然后清除ViewModel * 如果ViewModelStore的拥有者(Activity/Fragment)销毁后不会重建,那么就需要调用此方法 */ public final void clear() { for (ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); } } ``` ### 配置更改重建后ViewModel依然存在 * 如果存储器是空,就先尝试 从lastNonConfigurationInstance从获取 * mLastNonConfigurationInstances是在Activity的attach方法中赋值 * ActivityThread 中的 ActivityClientRecord 是不受 activity 重建的影响,那么ActivityClientRecord中lastNonConfigurationInstances也不受影响 # MVVM ![](https://img.kancloud.cn/45/10/4510e5022d43b4c4c729a6bdaf70aba3_826x446.png) **MVVM各职责如下**: * Model模型:业务相关的数据(如网络请求数据、本地数据库数据等)及其对数据的处理 * View视图:页面视图(Activity/Fragment),负责接收用户输入、发起数据请求及展示结果页面 * ViewModel:M与V之间的桥梁,负责业务逻辑 **MVVM特点**: * View层接收用户操作,并通过持有的ViewModel去处理业务逻辑,请求数据; * ViewModel层通过Model去获取数据,然后Model又将最新的数据传回ViewModel层,到这里,ViewModel与Presenter所做的事基本是一样的。但是ViewModel不会也不能持有View层的引用,而是View层会通过观察者模式监听ViewModel层的数据变化,当有新数据时,View层能自动收到新数据并刷新界面。 #### UI驱动 vs 数据驱动 `MVP`中,`Presenter`中需要持有`View`层的引用,当数据变化时,需要主动调用`View`层对应的方法将数据传过去并进行UI刷新,这种可以认为是UI驱动;而`MVVM`中,`ViewModel`并不会持有`View`层的引用,`View`层会监听数据变化,当`ViewModel`中有数据更新时,`View`层能直接拿到新数据并完成UI更新,这种可以认为是数据驱动,显然,`MVVM`相比于`MVP`来说更加解耦。 # MVI架构 本质就是`单向数据流动`+`状态集中管理` (谷歌推荐的最佳实践) ## 单向数据流动 新`UI State`主要分为以下几步: 1. `ViewModel` 会存储并公开`UI State`。`UI State`是经过`ViewModel`转换的应用数据。 2. `UI`层会向`ViewModel`发送用户事件通知。 3. `ViewModel`会处理用户操作并更新`UI State`。 4. 更新后的状态将反馈给`UI`以进行呈现。 5. 系统会对导致状态更改的所有事件重复上述操作。 ![](https://img.kancloud.cn/63/ff/63ff5a87bc977af093511c58f7aad69b_1852x1656.png) ### 优势 单向数据流动可以实现关注点分离原则,它可以将状态变化来源位置、转换位置以及最终使用位置进行分离。 这种分离可让`UI`只发挥其名称所表明的作用:通过观察`UI State`变化来显示页面信息,并将用户输入传递给`ViewModel`以实现状态刷新。 换句话说,单向数据流动有助于实现以下几点: 1. 数据一致性。界面只有一个可信来源。 2. 可测试性。状态来源是独立的,因此可独立于界面进行测试。 3. 可维护性。状态的更改遵循明确定义的模式,即状态更改是用户事件及其数据拉取来源共同作用的结果。 ## UIState 集中管理 ### 缺点 * 不相关的数据类型:`UI`所需的某些状态可能是完全相互独立的。在此类情况下,将这些不同的状态捆绑在一起的代价可能会超过其优势,尤其是当其中某个状态的更新频率高于其他状态的更新频率时。 * `UiState diffing`:`UiState` 对象中的字段越多,数据流就越有可能因为其中一个字段被更新而发出。由于视图没有 `diffing` 机制来了解连续发出的数据流是否相同,因此每次发出都会导致视图更新。当然,我们可以对 `LiveData` 或`Flow`使用 `distinctUntilChanged()` 等方法来实现局部刷新,从而解决这个问题 ### UI State实现局部刷新 下面我们就来看下`LiveData`怎么实现属性监听 ~~~kotlin //监听一个属性 fun <T, A> LiveData<T>.observeState( lifecycleOwner: LifecycleOwner, prop1: KProperty1<T, A>, action: (A) -> Unit ) { this.map { StateTuple1(prop1.get(it)) }.distinctUntilChanged().observe(lifecycleOwner) { (a) -> action.invoke(a) } } //监听两个属性 fun <T, A, B> LiveData<T>.observeState( lifecycleOwner: LifecycleOwner, prop1: KProperty1<T, A>, prop2: KProperty1<T, B>, action: (A, B) -> Unit ) { this.map { StateTuple2(prop1.get(it), prop2.get(it)) }.distinctUntilChanged().observe(lifecycleOwner) { (a, b) -> action.invoke(a, b) } } internal data class StateTuple1<A>(val a: A) internal data class StateTuple2<A, B>(val a: A, val b: B) //更新State fun <T> MutableLiveData<T>.setState(reducer: T.() -> T) { this.value = this.value?.reducer() } ~~~ 1. 如上所示,主要是添加一个扩展方法,也是通过`distinctUntilChanged`来实现防抖 2. 如果需要监听多个属性,例如两个属性有其中一个变化了就触发刷新,也支持传入两个属性 3. 需要注意的是`LiveData`默认是不防抖的,这样改造后就是防抖的了,所以传入相同的值是不会回调的 4. 同时需要注意下承载`State`的数据类需要防混淆 # 参考资料 [Google 推荐使用 MVI 架构?卷起来了~](https://juejin.cn/post/7048980213811642382) [“终于懂了“系列:Jetpack AAC完整解析(一)Lifecycle 完全掌握!](https://blog.csdn.net/hfy8971613/article/details/109641527) [Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus](https://tech.meituan.com/2018/07/26/android-livedatabus.html)