- 原文地址:ViewModels and LiveData: Patterns + AntiPatterns
- 原文做者:Jose Alcérreca
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:boileryao
- 校对者:Zhiw miguoer
用 Architecture Components 构建的 APP 中实体的典型交互 html
理想状况下,ViewModel 不该该知道任何关于 Android 的事情(如Activity、Fragment)。 这样会大大改善可测试性,有利于模块化,而且可以减小内存泄漏的风险。一个通用的法则是,你的 ViewModel 中没有导入像 android.*
这样的包(像 android.arch.*
这样的除外)。这个经验也一样适用于 MVP 模式中的 Presenter 。前端
❌ 不要让 ViewModel(或Presenter)直接使用 Android 框架内的类java
条件语句、循环和通常的断定等语句应该在 ViewModel 或者应用程序的其余层中完成,而不是在 Activity 或 Fragment 里。视图层一般是没有通过单元测试的(除非你用上了 Robolectric),因此在里面写的代码越少越好。View 应该仅仅负责展现数据以及发送各类事件给 ViewModel 或 Presenter。这被称为 Passive View 模式。(忧郁的 View,哈哈哈)react
✅ 保持 Activity 和 Fragment 中的逻辑代码最小化android
ViewModel 的生命周期跟 Activity 和 Fragment 不同。当 ViewModel 正在工做的时候,一个 Activity 可能处于本身 生命周期 的任何状态。 Activity 和 Fragment 能够被销毁而且从新建立, ViewModel 将对此一无所知。ios
ViewModel 对配置的从新加载(好比屏幕旋转)具备“抗性” ↑git
把视图层(Activity 或 Fragment)的引用传递给 ViewModel 是有 至关大的风险 的。假设 ViewModel 从网络请求数据,而后因为某些问题,数据返回的时候已经沧海桑田了。这时候,ViewModel 引用的视图层可能已经被销毁或者不可见了。这将产生内存泄漏甚至引发崩溃。github
❌ 避免在 ViewModel 里持有视图层的引用算法
推荐使用观察者模式做为 ViewModel 层和 View 层的通讯方式,可使用 LiveData 或者其余库中的 Observable 对象做为被观察者。数据库
一个很方便的设计 Android 应用中的展现层的方法是让视图层(Activity 或 Fragment)去观察 ViewModel 的变化。因为 ViewModel 对 Android 一无所知,它也就不知道 Android 是多么频繁的干掉视图层的小伙伴。这样有几个好处:
NullPointerException
。private void subscribeToModel() {
// Observe product data
viewModel.getObservableProduct().observe(this, new Observer<Product>() {
@Override
public void onChanged(@Nullable Product product) {
mTitle.setText(product.title);
}
});
}复制代码
Activity / Fragment 中的一个典型“订阅”案例。
✅ 让 UI 观察数据的变化,而不是直接向 UI 推送数据
能减轻你的担忧的主意必定是个好主意。若是你的 ViewModel 里代码太多、承担了太多职责,试着去:
✅ 把代码职责分散出去。若是须要的话,加上一个 Domain 层。
就像 Guide to App Architecture(应用架构指南) 里说的那样,大多数 APP 有多个数据源,好比:
在应用中放一个数据层是一个好主意,数据层彻底不关心展现层(MVP
中的 P
)。因为保持缓存和数据库与网络同步的算法一般很琐碎复杂,因此建议为每一个仓库建立一个类做为处理同步的单一入口。
若是是许多种而且差异很大的数据模型,考虑使用多个数据仓库。
✅ 添加数据仓库做为数据访问的单一入口。
考虑一下这种状况:你正在观察一个 ViewModel 暴露出来的 LiveData,它包含了一个待显示数据的列表。视图层该如何区分被加载的数据,网络错误和空列表呢?
LiveData<MyDataState>
。 MyDataState
可能包含数据是正在加载仍是已经加载成功、失败的信息。能够将类中有状态和其余元数据(好比错误信息)的数据封装到一个类。参见示例代码中的 Resource 类。
✅ 使用一个包装类或者 LiveData 来暴露状态信息。
Activity 的状态是指在 Activity 消失时从新建立屏幕内容所需的信息,Activity 消失意味着被销毁或进程被终止。旋转屏幕是最明显的状况,咱们已经在 ViewModel 部分提到了。保存在 ViewModel 的状态是安全的。
可是,你可能须要在其余 ViewModel 也消失的场景中恢复状态。例如,当操做系统因资源不足杀死进程时。
为了高效地保存和恢复 UI 状态,组合使用 onSaveInstanceState()
和 ViewModel。
这里有个示例:ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders
咱们管只发生一次的操做叫作事件。 ViewModels 暴露数据,但对于事件怎么样呢?例如,导航事件或显示 Snackbar 消息等应该仅被执行一次的操做。
事件的概念并不能和 LiveData 存取数据的方式完美匹配。来看下面这个从 ViewModel 中取出来的字段:
LiveData<String> snackbarMessage = new MutableLiveData<>();复制代码
一个 Activity 开始观察这个字段,ViewModel 完成了一个操做,因此须要更新消息:
snackbarMessage.setValue("Item saved!");复制代码
显然,Activity 接收到这个值后会显示出来一个 SnackBar。
可是,若是用户旋转手机,则新的 Activity 被建立并开始观察这个字段。当对 LiveData 的观察开始时,Activity 会当即收到已经使用过的值,这将致使消息再次显示!
在示例中,咱们继承 LiveData 建立一个叫作 SingleLiveEvent 的类来解决这个问题。它仅仅发送发生在订阅后的更新,要注意的是这个类只支持一个观察者。
✅ 使用像 SingleLiveEvent 这样的 observable 来处理导航栏或者 SnackBar 显示消息这样的状况
响应式范例在 Android 中运行良好,它容许在 UI 和应用程序的其余层之间创建方便的联系。 LiveData 是这个架构的关键组件,所以一般你的 Activity 和 Fragment 会观察 LiveData 实例。
ViewModel 如何与其余组件进行通讯取决于你,但要注意泄漏问题和边界状况。看下面这个图,其中 Presenter 层使用观察者模式,数据层使用回调:
UI 中的观察者模式和数据层中的回凋
若是用户退出 APP,视图就消失了因此 ViewModel 也没有观察者了。若是数据仓库是个单例或者是和 Application 的生命周期绑定的,这个数据仓库在进程被杀掉以前都不会被销毁。这只会发生在系统须要资源或用户手动杀死应用程序时,若是数据仓库在 ViewModel 中持有对回调的引用,ViewModel 将发生暂时的内存泄漏。
Activity 已经被销毁了可是 ViewModel 还在苟且
若是是一个轻量级 ViewModel 或能够保证操做快速完成,这个泄漏并非什么大问题。可是,状况并不老是这样。理想状况下,ViewModels 在没有任何观察者的状况下不该该持有 ViewModel 的引用:
实现这种机制有不少方法:
✅ 考虑边界状况,泄漏以及长时间的操做会对架构中的实例带来哪些影响。
❌ 不要将保存原始状态和数据相关的逻辑放在 ViewModel 中。任何从 ViewModel 所作的调用均可能是数据相关的。
为了不泄露 ViewModel 和回调地狱(嵌套的回凋造成的“箭头”代码),能够像这样观察数据仓库:
当 ViewModel 被移除或者视图的生命周期结束,订阅被清除:
若是尝试这种方法,有个问题:若是没法访问 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(id)
调用。
✅ 当须要在 ViewModel 中须要 Lifecycle 对象时,使用 Transformation 多是个好办法。
LiveData 最多见的用例是在 ViewModel 中使用 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 开始被观察才加载数据。一些通用的模式是这样的:
start()
方法,并尽早调用这个方法。 (参见Blueprints example )❌ 一般不用拓展 LiveData。可让 Activity 或 Fragment 告诉 ViewModel 何时开始加载数据。
[^是否须要关于 Architecture Component 的其余任何主题的指导(或意见)?留下评论!]:
感谢 Lyla Fujiwara、Daniel Galpin、Wojtek Kaliciński 和 Florina Muntenescu。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。