阅读 96

译 ViewModels and LiveData: Patterns + AntiPatterns

官网已经对ViewModel做了一些说明,比如不能在ViewModel中引入Activity的Context,但是还有很多注意事项,或者说idioms(惯用语法)来更好的使用ViewModel。

本文参考自Google官网推荐的一篇博文:ViewModels and LiveData: Patterns + AntiPatterns

先来一张官网给出的使用架构组件的整体描述图:

理想情况下,ViewModels不应该引入Android任何东西。 这提高了可测试性,泄漏安全性和模块化。 一般的经验法则是确保ViewModel中没有android .*导入(android.arch.*除外)。 这同样适用于presenter层。

❌  注意点1: 不要在ViewModel(或者presenter层)引入android的framework类

条件语句,循环和一般决策应在ViewModel或应用程序的其他层中完成,而不是在Activity或Fragment中完成。 View通常不经过单元测试(除非你使用Robolectric)所以代码行越少越好。 VIew视图应该只知道如何显示数据,并将用户事件发送到ViewModel(或Presenter)。 这称为Passive View被动视图模式。

即:在Activity和fragment中应该保留最小化的逻辑代码。

❌ 注意点2: 在《ViewModel官网学习总结》一文中,已经说明了,由于ViewModel的生命周期大于Activity的,所以不能在ViewModel层中引入Activity或者fragment的context,以避免内存泄漏或者crash。

✅  :ViewModels和View之间的交互,建议使用的方式是观察者模式,比如使用LiveData或者其他库中的观察者。即不要直接将数据传递到UI层,而是让UI层观察数据的变化,即数据驱动UI。

避免臃肿的ViewModels 参考:Lifecycle Aware Data Loading with Architecture Components

✅  :即避免把数据处理逻辑直接放到ViewModel中,而是放到LiveData(或者其他的observable)中,因为ViewModels设计的目的就是用来创建和组织LiveData的。可以将一些逻辑转移到presenter层中。

✅  :将逻辑封装在LiveData中,也有利于在很多ViewModels中复用相同的LiveData,,通过MediatorLiveData将多个LiveData源组合在一起,或者在一个Service中使用它们

参见以下代码:

''' public class JsonViewModel extends AndroidViewModel { // You probably have something more complicated // than just a String. Roll with me private final MutableLiveData<List> data = new MutableLiveData<List>(); public JsonViewModel(Application application) { super(application); loadData(); } public LiveData<List> getData() { return data; } private void loadData() { new AsyncTask<Void,Void,List>() { @Override protected List doInBackground(Void... voids) { File jsonFile = new File(getApplication().getFilesDir(), "downloaded.json"); List data = new ArrayList<>(); // Parse the JSON using the library of your choice return data; } @Override protected void onPostExecute(List data) { this.data.setValue(data); } }.execute(); } }''' 以上代码,将很多数据获取或者处理逻辑放到了ViewModel层中,这是不合理的,应该改成如下的方式:

public class JsonViewModel extends AndroidViewModel { private final JsonLiveData data; public JsonViewModel(Application application) { super(application); data = new JsonLiveData(application); } public LiveData<List> getData() { return data; } } public class JsonLiveData extends LiveData<List> { private final Context context; public JsonLiveData(Context context) { this.context = context; loadData(); } private void loadData() { new AsyncTask<Void,Void,List>() { @Override protected List doInBackground(Void… voids) { File jsonFile = new File(getApplication().getFilesDir(), "downloaded.json"); List data = new ArrayList<>(); // Parse the JSON using the library of your choice return data; } @Override protected void onPostExecute(List data) { setValue(data); } }.execute(); } } 现在我们的ViewModel变得相当简单。我们的LiveData现在完全封装了加载过程,只加载一次数据。

使用数据repository层 根据参考博客中的描述,其实官网文档中也有介绍,当数据可能来源于网络,或者本地内存,或者缓存等,这时候应该添加一个repository层,封装这些数据处理操作,然后presentation层(presenter或者ViewModels)就不用关心数据具体来源于哪里,repository层是数据对外的统一入口。

✅  :即:添加一个数据的repository层,作为外部获取数据的唯一入口。

处理数据的状态 请考虑以下情形:您正在观察由ViewModel公开的LiveData,该ViewModel包含要显示的一组列表数据。 视图如何区分加载的数据,网络错误和空列表? 您可以从ViewModel公开LiveData 。 例如,MyDataState可以包含有关数据当前是否正在加载,是否已成功加载或失败的信息,或者其他一些原始数据信息。

✅  :即:使用一个包装类来暴露出你的数据的状态信息,或者使用另外一个LiveData。

保存Activity的状态 Activity状态是Activity消失后重新创建屏幕所需的信息,表示Activity已被破坏或进程被终止。 旋转是最明显的情况,我们已经用ViewModels覆盖了它。 如果状态保存在ViewModel中,则状态是安全的。 但是,您可能需要在ViewModel也已消失的其他方案中恢复状态:例如,当操作系统资源不足并导致您的进程终止时。 要有效地保存和恢复UI状态,请使用persistence、onSaveInstanceState()和ViewModels的组合。 有关示例,请参阅:ViewModels:Persistence,onSaveInstanceState(),还原UI状态和加载程序

Event 事件 这里指的事件是只发生一次的。 ViewModels公开数据,但事件呢? 例如,导航事件、权限申请、或者显示Snackbar消息是应该只执行一次的操作。 事件的概念与LiveData存储和恢复数据的方式不完全吻合。 考虑具有以下字段的ViewModel:

LiveData snackbarMessage = new MutableLiveData<>(); Activity开始观察此操作,ViewModel完成操作,因此需要更新消息:

snackbarMessage.setValue("Item saved!"); 这时Activity获取到值的改变,并显示Snackbar,看上去没有什么问题,但是如果旋转屏幕,新的activity被创建,并会接收到旧的值,会导致Snackbar再次显示该message。

要解决这个问题,不应该用第三方库或者对架构组件进行扩展,而应该把事件当成状态的一部分。

即:把事件设计成状态的一部分。更多信息参考:LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case).

注意ViewModel的泄漏 考虑以下场景:

其中Presentation层使用观察者模式,而数据层使用接口回调。

如果用户退出app,View消失,ViewModel不再被观察,如果repository层是一个单例或者跟application生命周期一样,那么repository不会被销毁,直到进程被杀掉。而只有在系统资源紧张或者用户手动杀掉时,进程才会结束。而如果repository层如果有ViewModel的回调,则ViewModel会暂时性的发生内存泄漏。

如果ViewModel比较轻量级或者操作能很快结束,不会有很大影响。但事实并非总是如此。理想的情况是,只要没有任何View观察ViewModel,那么ViewModel就应该被销毁。

有很多方法可以做到这点:

通过ViewModel的onCleared()方法,可以告诉repository层,丢掉往ViewModel的回调。 在repository中,可以使用弱引用或者可以使用EventBus(两者都容易被误用甚至被认为是有害的) 像使用LiveData在View和ViewModel之间交互一样,使用LiveData在ViewModel和repository层之间交互 ✅  :要考虑到边界情况,泄漏和耗时操作对架构中的实例对象的影响。

在repository层中使用LiveData 为了避免泄漏ViewModel和回调地狱,repository层可以像这样被观察:

当ViewModel被清除的时候,或者View的生命周期结束的时候,ViewModel和repository间的订阅关系就被清理掉了。

如果您尝试这种方法,有一个问题:如果您无权访问LifecycleOwner,您如何从ViewModel订阅repository? 使用转换Transformations是解决此问题的一种非常方便的方法。 Transformations.switchMap允许您创建一个新的LiveData,以响应其他LiveData实例的更改。 它还允许在整个链中携带观察者生命周期信息:

LiveData repo = Transformations.switchMap(repoIdLiveData, repoId -> { if (repoId.isEmpty()) { return AbsentLiveData.create(); } return repository.loadRepo(repoId); } ); 在此示例中,当触发器获得更新时,将应用该函数并在下游调度结果。 Activity将观察repo,并且相同的LifecycleOwner将用于repository.loadRepo(id)调用。

✅  即:当你需要在ViewModel中获取一个Lifecycle生命周期的对象时,这时候可以使用Transformations

扩展LiveData 通常使用LiveData的方式,是在ViewModel中使用MutableLiveData,并暴露出一个get方法。

如果您需要更多功能,扩展LiveData会在有活跃观察者时通知您。 例如,当您想要开始收听位置或传感器服务时,这非常有用:

public class MyLiveData extends LiveData {

public MyLiveData(Context context) {
    // Initialize service
}

@Override
protected void onActive() {
    // Start listening
}

@Override
protected void onInactive() {
    // Stop listening
}
复制代码

} 什么时候不应该扩展LiveData 您还可以使用onActive()来启动一些加载数据的服务,但除非您有充分的理由,否则您无需等待LiveData直到能被观察到。 一些常见的模式:

将一个start()方法添加到ViewModel并尽快调用它[参见Google官方实例 Blueprints example] 设置一个开始加载的属性[请参阅 GithubBrowserExample]。 ❌  您通常不会扩展LiveData。 让您的Activity或Fragment告诉ViewModel什么时候开始加载数据

最后:

关于使用ViewModel,官网推荐了一篇博客:ViewModels : A Simple Example

一个Android codelab中关于使用ViewModel的例子:codelabs.developers.google.com/codelabs/an…

YouTube上一个官方对ViewModel的使用介绍:www.youtube.com/watch?v=c9-…

Android Jetpack: ViewModel | 中文教学视频 :mp.weixin.qq.com/s/uGWH1os8K…

对于使用 Architecture Components,官网给出了一些简单的例子:

github.com/googlesampl…

以及一个官网推荐的使用Jetpack的例子:  Sunflower

一个使用了生命周期组件的MVVM版本的GitHub客户端 Github Browser Sample with Android Architecture Components

This is a sample app that uses Android Architecture Components with Dagger 2.

NOTE It is a relatively more complex and complete example so if you are not familiar with Architecture Components, you are highly recommended to check other examples in this repository first.

官网介绍这是一个相对更加复杂和完备的例子,需要对依赖注入框架Dagger2有一定的了解,非常非常非常推荐大家来学习该例子。

摘录自google官网,过内翻译博文