【Medium 万赞好文】ViewModel 和 LiveData:模式 + 反模式

简介: 【Medium 万赞好文】ViewModel 和 LiveData:模式 + 反模式

image.png

View 和 ViewModel


分配责任

理想情况下,ViewModel 应该对 Android 世界一无所知。这提升了可测试性,内存泄漏安全性,并且便于模块化。 通常的做法是保证你的 ViewModel 中没有导入任何 android.*android.arch.* (译者注:现在应该再加一个 androidx.lifecycle)除外。 这对 Presenter(MVP) 来说也一样。


❌   不要让 ViewModel  和 Presenter 接触到 Android 框架中的类

条件语句,循环和通用逻辑应该放在应用的 ViewModel 或者其它层来执行,而不是在 Activity 和 Fragment 中。 View 通常是不进行单元测试的,除非你使用了 Robolectric,所以其中的代码越少越好。 View 只需要知道如何展示数据以及向 ViewModel/Presenter 发送用户事件。这叫做 Passive View 模式。

✅  让 Activity/Fragment 中的逻辑尽量精简


ViewModel 中的 View 引用

ViewModel 和 Activity/Fragment 具有不同的作用域。当 Viewmodel 进入 alive 状态且在运行时,activity 可能位于 生命周期状态 的任何状态。 Activitie 和 Fragment 可以在 ViewModel 无感知的情况下被销毁和重新创建。

image.png

向 ViewModel 传递 View(Activity/Fragment) 的引用是一个很大的冒险。假设 ViewModel 请求网络,稍后返回数据。 若此时 View 的引用已经被销毁,或者已经成为一个不可见的 Activity。这将导致内存泄漏,甚至 crash。

❌  避免在 ViewModel 中持有 View 的引用

在 ViewModel 和 View 中通信的建议方式是观察者模式,使用 LiveData 或者其他类库中的可观察对象。


观察者模式

image.png

在 Android 中设计表示层的一种非常方便的方法是让 View 观察和订阅 ViewModel(中的变化)。 由于 ViewModel 并不知道 Android 的任何东西,所以它也不知道 Android 是如何频繁的杀死 View 的。 这有如下好处:

  1. ViewModel 在配置变化时保持不变,所以当设备旋转时不需要再重新请求资源(数据库或者网络)。
  2. 当耗时任务执行结束,ViewModel 中的可观察数据更新了。这个数据是否被观察并不重要,尝试更新一个 不存在的 View 并不会导致空指针异常。
  3. ViewModel 不持有 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


胖 ViewModel


无论是什么让你选择分层,这总是一个好主意。如果你的 ViewModel 拥有大量的代码,承担了过多的责任,那么:

  • 移除一部分逻辑到和 ViewModel 具有同样作用域的地方。这部分将和应用的其他部分进行通信并更新 ViewModel 持有的 LiveData。
  • 采用 Clean Architecture,添加一个 domain 层。这是一个可测试,易维护的架构。Architecture Blueprints 中有 Clean Architecture 的示例。

✅  分发责任,如果需要的话,添加 domain 层


使用数据仓库

应用架构指南 中所说,大部分 App 有多个数据源:

  1. 远程:网络或者云端
  2. 本地:数据库或者文件
  3. 内存缓存


在你的应用中拥有一个数据层是一个好主意,它和你的视图层完全隔离。保持缓存和数据库与网络同步的算法并不简单。建议使用单独的 Repository 类作为处理这种复杂性的单一入口点.

如果你有多个不同的数据模型,考虑使用多个 Repository 仓库。

✅ 添加数据仓库作为你的数据的单一入口点。


处理数据状态


考虑下面这个场景:你正在观察 ViewModel 暴露出来的一个 LiveData,它包含了需要显示的列表项。那么 View 如何区分数据已经加载,网络错误和空集合?

  • 你可以通过 ViewModel 暴露出一个 LiveData<MyDataState>MyDataState 可以包含数据正在加载,已经加载完成,发生错误等信息。
  • 你可以将数据包装在具有状态和其他元数据(如错误消息)的类中。查看示例中的 Resource 类。

✅ 使用包装类或者另一个 LiveData 来暴露数据的状态信息


保存 activity 状态


当 activity 被销毁或者进程被杀导致 activity 不可见时,重新创建屏幕所需要的信息被称为 activity 状态。屏幕旋转就是最明显的例子,如果状态保存在 ViewModel 中,它就是安全的。


但是,你可能需要在 ViewModel 也不存在的情况下恢复状态,例如当操作系统由于资源紧张杀掉你的进程时。

为了有效的保存和恢复 UI 状态,使用 onSaveInstanceState() 和 ViewModel 组合。

详见:ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders


Event

Event 指只发生一次的事件。ViewModel 暴露出的是数据,那么 Event 呢?例如,导航事件或者展示 Snackbar 消息,都是应该只被执行一次的动作。


LiveData 保存和恢复数据,和 Event 的概念并不完全符合。看看具有下面字段的一个 ViewModel:

LiveData<String> snackbarMessage = new MutableLiveData<>();
复制代码


Activity 开始观察它,当 ViewModel 结束一个操作时需要更新它的值:

snackbarMessage.setValue("Item saved!");
复制代码


Activity 接收到了值并且显示了 SnackBar。显然就应该是这样的。

但是,如果用户旋转了手机,新的 Activity 被创建并且开始观察。当对 LiveData 的观察开始时,新的 Activity 会立即接收到旧的值,导致消息再次被显示。


与其使用架构组件的库或者扩展来解决这个问题,不如把它当做设计问题来看。我们建议你把事件当做状态的一部分。

把事件设计成状态的一部分。更多细节请阅读 LiveData with SnackBar,Navigation and other events (the SingleLiveEvent case)


ViewModel 的泄露


得益于方便的连接 UI 层和应用的其他层,响应式编程在 Android 中工作的很高效。LiveData 是这个模式的关键组件,你的 Activity 和 Fragment 都会观察 LiveData 实例。

LiveData 如何与其他组件通信取决于你,要注意内存泄露和边界情况。如下图所示,视图层(Presentation Layer)使用观察者模式,数据层(Data Layer)使用回调。

image.png

当用户退出应用时,View 不可见了,所以 ViewModel 不需要再被观察。如果数据仓库 Repository 是单例模式并且和应用同作用域,那么直到应用进程被杀死,数据仓库 Repository 才会被销毁。 只有当系统资源不足或者用户手动杀掉应用这才会发生。如果数据仓库 Repository 持有 ViewModel 的回调的引用,那么 ViewModel 将会发生内存泄露。

image.png

如果 ViewModel 很轻量,或者保证操作很快就会结束,这种泄露也不是什么大问题。但是,事实并不总是这样。理想情况下,只要没有被 View 观察了,ViewModel 就应该被释放。

image.png

你可以选择下面几种方式来达成目的:

  • 通过 ViewModel.onCLeared() 通知数据仓库释放 ViewModel 的回调
  • 在数据仓库 Repository 中使用 弱引用 ,或者 Event Bu(两者都容易被误用,甚至被认为是有害的)。
  • 通过在 View 和 ViewModel 中使用 LiveData 的方式,在数据仓库和 ViewModel 之间进程通信

✅  考虑边界情况,内存泄露和耗时任务会如何影响架构中的实例。

❌   不要在 ViewModel 中进行保存状态或者数据相关的核心逻辑。 ViewModel 中的每一次调用都可能是最后一次操作。


数据仓库中的 LiveData



为了避免 ViewModel 泄露和回调地狱,数据仓库应该被这样观察:

image.png

当 ViewModel 被清除,或者 View 的生命周期结束,订阅也会被清除:

image.png

如果你尝试这种方式的话会遇到一个问题:如果不访问 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,那么同样的 LifecycleOwner 将被应用在 repository.loadRepo(repoId) 的调用上。

无论什么时候你在 ViewModel 内部需要一个 LifeCycle 对象时,Transformation 都是一个好方案。


继承 LiveData


在 ViewModel 中使用 LiveData 最常用的就是 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 被观察。下面这些通用的设计模式:

你并不需要经常继承 LiveData 。让 Activity 和 Fragment 告诉 ViewModel 什么时候开始加载数据。


分割线


翻译就到这里了,其实这篇文章已经在我的收藏夹里躺了很久了。 最近 Google 重写了 Plaid 应用,用上了一系列最新技术栈, AAC,MVVM, Kotlin,协程 等等。这也是我很喜欢的一套技术栈,之前基于此开源了 Wanandroid 应用 ,详见 真香!Kotlin+MVVM+LiveData+协程 打造 Wanandroid!


当时基于对 MVVM 的浅薄理解写了一套自认为是 MVVM 的 MVVM 架构,在阅读一些关于架构的文章,以及 Plaid 源码之后,发现了自己的 MVVM 的一些认知误区。后续会对 Wanandroid 应用进行合理改造,并结合上面译文中提到的知识点作一定的说明。欢迎 Star !



相关文章
|
7月前
|
存储 移动开发 数据库
构建高效Android应用:探究LiveData和ViewModel的最佳实践
【4月更文挑战第20天】 在动态演化的移动开发领域,构建一个既响应迅速又能够在用户界面保持稳定的Android应用是至关重要的。近年来,随着Android架构组件的推出,特别是LiveData和ViewModel的引入,开发者得以更有效地管理应用状态并优化用户界面的响应性。本文将深入探讨LiveData和ViewModel的实现机制,并通过案例分析展示如何结合它们来构建一个高效且健壮的Android应用架构。我们将重点讨论如何通过这些组件简化数据绑定过程、提高代码的可维护性和测试性,同时确保用户界面的流畅性。
|
7月前
|
前端开发 测试技术 API
Jetpack MVVM 七宗罪之六:ViewModel 接口暴露不合理
Jetpack MVVM 七宗罪之六:ViewModel 接口暴露不合理
91 0
|
XML 缓存 前端开发
Android 架构之 MVI 初级体 | Flow 替换 LiveData 重构数据链路(下)
Android 架构之 MVI 初级体 | Flow 替换 LiveData 重构数据链路
481 0
|
7月前
|
前端开发 JavaScript Android开发
Jetpack MVVM 七宗罪之四: 使用 LiveData/StateFlow 发送 Events
Jetpack MVVM 七宗罪之四: 使用 LiveData/StateFlow 发送 Events
182 0
|
4月前
|
前端开发 开发者 设计模式
揭秘Uno Platform状态管理之道:INotifyPropertyChanged、依赖注入、MVVM大对决,帮你找到最佳策略!
【8月更文挑战第31天】本文对比分析了 Uno Platform 中的关键状态管理策略,包括内置的 INotifyPropertyChanged、依赖注入及 MVVM 框架。INotifyPropertyChanged 方案简单易用,适合小型项目;依赖注入则更灵活,支持状态共享与持久化,适用于复杂场景;MVVM 框架通过分离视图、视图模型和模型,使状态管理更清晰,适合大型项目。开发者可根据项目需求和技术栈选择合适的状态管理方案,以实现高效管理。
52 0
|
5月前
LiveData和ViewModel源码学习
LiveData和ViewModel源码学习
|
6月前
|
设计模式 前端开发 JavaScript
简述mvvm模式
简述mvvm模式
|
7月前
|
数据库 Android开发 开发者
实现高效安卓应用:探究LiveData和ViewModel的最佳实践
【4月更文挑战第19天】 在构建响应式的Android应用程序时,LiveData和ViewModel是两个核心组件。它们不仅提供了数据持有和界面更新的机制,还促进了组件间的解耦。本文将深入探讨如何通过结合LiveData和ViewModel来优化应用架构,提升用户体验,并确保数据的一致性和生存期管理。我们将透过实际案例分析,揭示这些技术如何协同工作以应对复杂的UI场景,并展示如何在实际项目中实施这些最佳实践。
|
7月前
|
移动开发 前端开发 数据管理
构建高效Android应用:采用MVVM架构与LiveData的全面指南
在移动开发领域,构建一个既快速又可靠的应用对于开发者来说至关重要。随着Android Jetpack组件的推出,MVVM(Model-View-ViewModel)架构和LiveData已成为实现响应式、可测试且易于维护应用的首选解决方案。本文将深入探讨如何在Android应用中实施MVVM模式,以及如何利用LiveData来优化UI组件的数据更新流程,确保用户界面与业务逻辑之间的高度解耦和流畅交互。
142 4
|
7月前
|
存储 移动开发 数据处理
构建高效安卓应用:探究LiveData和ViewModel的实践之路
【4月更文挑战第12天】 在动态演化的移动开发世界中,Android平台的UI架构经历了多次革新。近年来,随着Jetpack组件的推广,LiveData和ViewModel成为了开发者们提升应用响应性和稳定性的有力工具。本文将深入探讨这两项技术的核心原理、实现细节及其在实际项目中的协同运用,旨在为读者提供一份清晰、实操性强的技术指南,帮助开发者构建出既符合现代用户体验又具备高效数据管理的Android应用。
39 0