原文做者: Jose Alcérreca原文地址: ViewModels and LiveData: Patterns + AntiPatternshtml
译者:秉心说java
理想状况下,ViewModel 应该对 Android 世界一无所知。这提高了可测试性,内存泄漏安全性,而且便于模块化。
一般的作法是保证你的 ViewModel 中没有导入任何 android.*
,android.arch.*
(译者注:如今应该再加一个 androidx.lifecycle
)除外。
这对 Presenter(MVP) 来讲也同样。android
❌ 不要让 ViewModel 和 Presenter 接触到 Android 框架中的类
条件语句,循环和通用逻辑应该放在应用的 ViewModel 或者其它层来执行,而不是在 Activity 和 Fragment 中。
View 一般是不进行单元测试的,除非你使用了 Robolectric,因此其中的代码越少越好。
View 只须要知道如何展现数据以及向 ViewModel/Presenter 发送用户事件。这叫作 Passive View 模式。git
✅ 让 Activity/Fragment 中的逻辑尽可能精简
ViewModel 和 Activity/Fragment
具备不一样的做用域。当 Viewmodel 进入 alive 状态且在运行时,activity 可能位于 生命周期状态 的任何状态。
Activitie 和 Fragment 能够在 ViewModel 无感知的状况下被销毁和从新建立。github
向 ViewModel 传递 View(Activity/Fragment) 的引用是一个很大的冒险。假设 ViewModel 请求网络,稍后返回数据。
若此时 View 的引用已经被销毁,或者已经成为一个不可见的 Activity。这将致使内存泄漏,甚至 crash。算法
❌ 避免在 ViewModel 中持有 View 的引用
在 ViewModel 和 View 中通讯的建议方式是观察者模式,使用 LiveData 或者其余类库中的可观察对象。数据库
在 Android 中设计表示层的一种很是方便的方法是让 View 观察和订阅 ViewModel(中的变化)。
因为 ViewModel 并不知道 Android 的任何东西,因此它也不知道 Android 是如何频繁的杀死 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 拥有大量的代码,承担了过多的责任,那么:segmentfault
移除一部分逻辑到和 ViewModel 具备一样做用域的地方。这部分将和应用的其余部分进行通讯并更新设计模式
ViewModel 持有的 LiveData。
✅ 分发责任,若是须要的话,添加 domain 层
如 应用架构指南 中所说,大部分 App 有多个数据源:
在你的应用中拥有一个数据层是一个好主意,它和你的视图层彻底隔离。保持缓存和数据库与网络同步的算法并不简单。建议使用单独的 Repository 类做为处理这种复杂性的单一入口点.
若是你有多个不一样的数据模型,考虑使用多个 Repository 仓库。
✅ 添加数据仓库做为你的数据的单一入口点。
考虑下面这个场景:你正在观察 ViewModel 暴露出来的一个 LiveData,它包含了须要显示的列表项。那么 View 如何区分数据已经加载,网络错误和空集合?
LiveData<MyDataState>
,MyDataState
能够包含数据正在加载,已经加载完成,发生错误等信息。✅ 使用包装类或者另外一个 LiveData 来暴露数据的状态信息
当 activity 被销毁或者进程被杀致使 activity 不可见时,从新建立屏幕所须要的信息被称为 activity 状态。屏幕旋转就是最明显的例子,若是状态保存在 ViewModel 中,它就是安全的。
可是,你可能须要在 ViewModel 也不存在的状况下恢复状态,例如当操做系统因为资源紧张杀掉你的进程时。
为了有效的保存和恢复 UI 状态,使用 onSaveInstanceState()
和 ViewModel 组合。
详见:[ViewModels: Persistence, onSaveInstanceState(), Restoring UI
State and Loaders](https://medium.com/google-dev... 。
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)
得益于方便的链接 UI 层和应用的其余层,响应式编程在 Android 中工做的很高效。LiveData 是这个模式的关键组件,你的 Activity 和 Fragment 都会观察 LiveData 实例。
LiveData 如何与其余组件通讯取决于你,要注意内存泄露和边界状况。以下图所示,视图层(Presentation Layer)使用观察者模式,数据层(Data Layer)使用回调。
当用户退出应用时,View 不可见了,因此 ViewModel 不须要再被观察。若是数据仓库 Repository 是单例模式而且和应用同做用域,那么直到应用进程被杀死,数据仓库 Repository 才会被销毁。 只有当系统资源不足或者用户手动杀掉应用这才会发生。若是数据仓库 Repository 持有 ViewModel 的回调的引用,那么 ViewModel 将会发生内存泄露。
若是 ViewModel 很轻量,或者保证操做很快就会结束,这种泄露也不是什么大问题。可是,事实并不老是这样。理想状况下,只要没有被 View 观察了,ViewModel 就应该被释放。
你能够选择下面几种方式来达成目的:
✅ 考虑边界状况,内存泄露和耗时任务会如何影响架构中的实例。❌ 不要在 ViewModel 中进行保存状态或者数据相关的核心逻辑。 ViewModel 中的每一次调用均可能是最后一次操做。
为了不 ViewModel 泄露和回调地狱,数据仓库应该被这样观察:
当 ViewModel 被清除,或者 View 的生命周期结束,订阅也会被清除:
若是你尝试这种方式的话会遇到一个问题:若是不访问 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 都是一个好方案。
在 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 } }
你也能够经过 onActive()
来开启服务加载数据。可是除非你有一个很好的理由来讲明你不须要等待 LiveData 被观察。下面这些通用的设计模式:
ViewModel
添加 start()
方法,并尽快调用它。见 [Blueprints example]你并不须要常常继承 LiveData 。让 Activity 和 Fragment 告诉 ViewModel 何时开始加载数据。
翻译就到这里了,其实这篇文章已经在个人收藏夹里躺了好久了。
最近 Google 重写了 Plaid 应用,用上了一系列最新技术栈, AAC,MVVM, Kotlin,协程 等等。这也是我很喜欢的一套技术栈,以前基于此开源了 Wanandroid 应用 ,详见 真香!Kotlin+MVVM+LiveData+协程 打造 Wanandroid! 。
当时基于对 MVVM 的浅薄理解写了一套自认为是 MVVM 的 MVVM 架构,在阅读一些关于架构的文章,以及 Plaid 源码以后,发现了本身的 MVVM 的一些认知误区。后续会对 Wanandroid 应用进行合理改造,并结合上面译文中提到的知识点做必定的说明。欢迎 Star !
文章首发微信公众号:秉心说
, 专一 Java 、 Android 原创知识分享,LeetCode 题解。更多最新原创文章,扫码关注我吧!