简评: MVVM 是谷歌提出的一种 Android 架构模式, 结合了 Data Binding 和一些生命周期组件 LiveData 和 ViewModel 等详情可以查看谷歌官方样例库
View 和 ViewModels
理论情况下, ViewModel 不需要知道任何关于 Android 的东西这提供了可测试性, 防止内存泄漏和模块化的好处一条基本规制是确保在你的 ViewModels 类中没有任何 android.* 的类导入 (android.arch.* 例外) 这点和 Presenter 一样
别让 ViewModels(和 Presenters)知道任何关于 Android 框架的类
条件语句, 循环和一般决策应该在 app 的 ViewModels 或其他层次中完成, 而不是在 Activity 或 Fragment 中 View 通常不是单元测试的 (除非你使用 Robolectric), 所以越少代码越好 View 仅仅应该知道如何展示数据以及发送用户的事件给 ViewModel(或 Presenter) 这就叫被动视图模式
在 Activities 和 Fragments 中保持最小化的逻辑
在 ViewModels 中引用 View
ViewModels 和 Activities 或 Fragments 相比有着不同的范围当一个 ViewModel 存活并运行时, 一个 Activity 可以处于它生命周期的任何一个状态在 ViewModel 不知道的情况下, Activities 和 Fragments 可以被再次摧毁和创建
ViewModels 在配置改变时仍然存在
将 View 的引用 (Activity 或 Fragment) 传递到 ViewModel 是一个严重的风险假设 ViewModel 中请求了网络数据, 数据在之后的一段时间返回此时, View 的引用有可能被回收或者旧的 Activity 不再可见, 这样就产生了内存泄漏, 甚至崩溃
避免在 ViewModels 中引用 Views
在 ViewModels 和 Views 中沟通推荐的方式是观察者模式, 利用 LiveData 或者其他库提供的可观察的方式
观察者模式
通过观察者模式, 在 Android 展示层中让 View(Activity 或 Fragment)观察(订阅改变) ViewModel 显得非常方便因为 ViewModel 不需要知道 Android 内容, 它也不知道 Android 是如何频繁地杀死 View 的好处在于:
ViewModels 在配置改变时依然存在, 因此当旋转屏幕时不需要再次查询内部数据(数据库或网络)
当一个长时间的操作结束时, 在 ViewModel 中可观察的部分更新了无论数据是否被观察了, 当尝试更新不存在的 View 时没有空指针异常的发生
ViewModels 不引用 View, 所以减少了内存泄漏的风险
- private void subscribeToModel() {
- // Observe product data
- viewModel.getObservableProduct().observe(this, new Observer<Product>() {
- @Override
- public void onChanged(@Nullable Product product) {
- mTitle.setText(product.title);
- }
- });
- }
在 UI 中注册数据推送, 让 UI 观察它的改变
臃肿的 ViewModels
如果你的 ViewModel 中放了太多代码或者太多的职责, 可以考虑:
将一些逻辑移动到 presenter, 它和 ViewModel 有相同的范围它可以和你 app 中的其他部分沟通并更新 ViewModel 中 LiveData 的持有者
添加一个领域层并采取简洁的架构, 这样有利于测试和维护, 也同样促进了快速脱离主线程在 架构蓝图中有一个简洁架构的样例
必要时分散职责, 增加领域层
使用数据仓库
在 App 架构指南中, 可以看到大多数应用都有多种数据来源, 比如:
远程: 网络或云
本地: 数据库或文件
内存缓存
在你的应用中拥有数据层是一个好主意, 展示层完全注意不到保留缓存同步数据库和网络的算法都是无关紧要的拥有一个完全隔离的仓库类作为一个单一的入口来处理这些复杂的事情是非常推荐的
如果你有多种并且不同的数据模型, 考虑添加多种仓库
添加一个数据仓库作为访问数据的单一入口
处理数据状态
考虑这个场景: 你正在观察 ViewModel 中暴露的 LiveData, 它包含一个展示项目的列表那么在数据加载, 网络错误或者空列表时, 视图该如何呈现这些变化呢?
你可以在 ViewModel 中暴露一个 LiveData<MyDataState > 例如, MyDataState 应该包含那些数据是否正在加载, 或者加载成功或加载失败的信息
你可以把数据包裹在一个保存了状态或其他元数据 (例如一条错误消息) 的类中看看我们样例中的 Resource 类
使用一些包裹类或另一个 LiveData 来暴露你数据的信息
保存 Activity 状态
Activity 状态是一些当 Activity 消失时 (意味着被回收或进程被杀死) 你需要重新恢复屏幕的信息旋转屏幕是一个最显著的案例, 还好我们有 ViewModel 状态保存在 ViewModel 中是安全的
然而在某些场景中, 当 ViewModel 也消失时, 你可能也需要恢复状态比如当操作系统的资源紧缺时, 可能会杀死你的进程
为了高效地保存和恢复 UI 状态, 需要结合持续性, onSaveInstanceState() 以及 ViewModels
看看例子: ViewModels: 持续, onSaveInstanceState(), 恢复 UI 状态和装载器
事件
一个事件指发送一次的动作 ViewModels 暴露了数据, 但什么是事件呢? 例如, 导航事件或者展示 Snackbar 消息都是应该只执行一次的动作
事件的概念并不能很好的展示 LiveData 是如何存储和恢复数据的来看一下下面的 ViewModel:
LiveData<String> snackbarMessage = new MutableLiveData<>();
一个 Activity 开始观察这个数据, 并且 ViewModel 完成了一个操作之后它需要更新这条消息:
snackbarMessage.setValue("Item saved!");
Activity 收到这条消息, 并展示在 Snackbar 中这显然没毛病
然而, 如果用户旋转手机, 创建了新的 Activity 并开始观察当 LiveData 观察发生后, Activity 立即收到了旧的值, 这时消息再次展示了!
我们扩展了 LiveData, 并创建了一个类叫 SingleLiveEvent, 作为刚刚问题的解决方案它仅仅发送订阅之后出现的更新注意它只支持一个观察者
为像导航或 Snackbar 消息等事件使用可观察的行为如 SingleLiveEvent
泄露 ViewModels
反应式范例在 Android 中工作得很好, 因为它允许在 UI 和应用的其他层次建立方便的连接 LiveData 是这个结构中的关键组件, 因此通常情况下你的 Activities 和 Fragments 都会观察一个 LiveData 实例
ViewModels 和其他组件是如何沟通的取决于你, 但要注意泄露和边界情况下图中展示层使用了观察者模式, 数据层使用了回调:
如果用户退出了应用, View 将会消失, 因此 ViewModel 不再被观察如果仓库是一个单例, 或者应用范围的, 那么仓库将不会回收直到进程被杀死这只会在操作系统需要资源或者用户手动杀死应用时才会发生如果仓库保留了 ViewModel 中回调的引用, 那么 ViewModel 就会暂时泄露
如果 ViewModel 存活或者被分配的操作很快就完成了, 那么这个泄露没什么然而, 不是所有的时候都这样理想情况下, ViewModels 应该在没有任何 Views 观察它们时回收:
你可以采取以下选项来达到这个目的:
使用 ViewModel.onCleared() 你可以告诉仓库扔掉 ViewModel 的回调
在仓库中你可以使用弱引用或者 EventBus(这两者都容易滥用甚至有害)
考虑边界情况, 泄露以及长时间的操作如何影响你架构中的实例
不要把保存清除状态或者相关的关键逻辑放在 ViewModel 中任何你从 ViewModel 中的调用都可能是最后一次
在仓库中的 LiveData
为了避免 ViewModels 的泄露和回调地狱, 仓库可以这样观察:
当 ViewModel 被清除时或者当 View 的生命周期结束时, 订阅也被清除了:
如果用这种方式的话有个 catch: 如果你不访问 LifecycleOwner, 那么你怎么从仓库订阅 ViewModel 呢? 使用 Transformations 可以很方便地解决这个问题 Transformations.switchMap 让你可以创建一个新的 LiveData, 可以对其他 LiveData 实例做出反应它也同样允许你通过链来携带观察者的生命周期信息:
- LiveData<Repo> repo = Transformations.switchMap(repoIdLiveData, repoId -> {
- if (repoId.isEmpty()) {
- return AbsentLiveData.create();
- }
- return repository.loadRepo(repoId);
- }
- );
在这个例子中, 当触发更新时, 函数就会被调用并将结果向下分发一个 Activity 可以观察 repo 并且随着
repository.loadRepo(id)
的调用相同的 LifecycleOwner 会被使用
无论何时, 当你考虑在 ViewModel 中使用 生命周期对象时, Transformation 都可能是个解决方案
继承 LiveData
LiveData 最普遍的用例就是在 ViewModels 中使用 MutableLiveData 并且将它们作为 LiveData 暴露出去, 使得它们在其他观察者中不可改变
如果你需要更多功能, 继承 LiveData 可以让你知道何时观察者正在活动当你想要开始监听定位或者传感器服务时这将会很有用例如:
- public class MyLiveData extends LiveData<MyData> {
- public MyLiveData(Context context) {
- // Initialize service
- }
- @Override
- protected void onActive() {
- // Start listening
- }
- @Override
- protected void onInactive() {
- // Stop listening
- }
- }
何时不继承 LiveData
你也可以使用 onActive() 开始一些加载数据的服务, 但除非你有一个很好的理由, 你没有必要等待 LiveData 被观察一些普遍的模式:
在 ViewModel 中添加一个 start() 方法, 并尽快调用它[查看蓝图例子]
设置一个启动负载的属性[查看 GithubBrowserExample]
通常你不用继承 LiveData 让你的 Activity 或者 Fragment 来告诉 ViewModel 什么时候开始加载数据
来源: https://juejin.im/entry/5a8fc34a5188255efc5f6b63