Databinding 是一种框架,MVVM是一种模式,两者的概念是不一样的。我的理解DataBinding是一个实现数据和UI绑定的框架,只是一个实现MVVM模式的工具。ViewModel和View可以通过DataBinding来实现单向绑定和双向绑定,这套UI和数据之间的动态监听和动态更新的框架Google已经帮我们做好了。在MVVM模式中ViewModel和View是用绑定关系来实现的,所以有了DataBinding 使我们构建Android MVVM 应用程序成为可能。
之前看了很多关于DataBinding的博客和相关的一些Demo,大多数就是往xml布局文件传入一些数据,然后把这些数据绑定到控件上,接着在这些控件上( 设置一些事件到控件上,基本讲述都是DataBinding的基本用法。但是并没有人告诉你把一个onClickListener 写到一个类并把这个listener绑定到xml里面上是不是不太好,也没有人告诉你这个和xml布局绑定的ViewModel类应该放哪些数据,应该做什么事?应该如何设计?更是很少有博文来告诉你在Android 中如何通过Data Binding 去构建MVVM 的应用框架。这也就是是本篇文章的重点。接下来,我们先来看看什么是MVVM,然后在一步一步来设计整个应用程序框架。
源码地址 https://github.com/Kelin-Hong/MVVMLight
首先,我们先大致了解Android开发中常见的模式,以便我们更深入了解MVVM 模式。
**View:**对应于xml布局文件
**Model:**实体模型
**Controllor:**对应于Activity业务逻辑,数据处理和UI处理
从上面看起来各个组件的职责视乎还挺耦合MVC的,但是打开Android的一个Activity文件,一看一言难尽, Android中经常会出现数千行的Activity代码,究其原因,Android中纯粹作为View的各个XML视图功能太弱,Activity基本上都是View和Controller的合体,既要负责视图的显示又要加入控制逻辑,承担的功能过多,代码量大也就不足为奇。所有更贴切的目前常规的开发说应该是View-Model 模式,大部分都是通过Activity的协调,连接,和处理逻辑的。
**View: **对应于Activity和xml,负责View的绘制以及与用户交互
**Model: **依然是实体模型
**Presenter: **负责完成View于Model间的交互和业务逻辑
在Android开发中MVP的设计思想用得比较多,利用MVP的设计模型可以把部分的逻辑的代码从Fragment和Activity业务的逻辑移出来,在Presenter中持有View(Activity或者Fragment)的引用,然后在Presenter调用View暴露的接口对视图进行操作,这样有利于把视图操作和业务逻辑分开来。MVP能够让Activity成为真正的View而不是View和Control的合体,Activity只做UI相关的事。但是这个模式还是存在一些不好的地方,比较如说:
Activity需要实现各种跟UI相关的接口,同时要在Activity中编写大量的事件,然后在事件处理中调用presenter的业务处理方法,View和Presenter只是互相持有引用并互相做回调,代码不美观。
这种模式中,程序的主角是UI,通过UI事件的触发对数据进行处理,更新UI就有考虑线程的问题。而且UI改变后牵扯的逻辑耦合度太高,一旦控件更改(比较TextView 替换 EditText等)牵扯的更新UI的接口就必须得换。
复杂的业务同时会导致presenter层太大,代码臃肿的问题。
**View: **对应于Activity和xml,负责View的绘制以及与用户交互
**Model: **实体模型
**ViewModel: **负责完成View于Model间的交互,负责业务逻辑
MVVM的目标和思想MVP类似,利用数据绑定(Data Binding)、依赖属性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一个更加灵活高效的架构。
通过上面对MVVM的简述和其他两种模式的对比,我们发现MVVM对比MVC和MVP来说还是存在比较大的优势,虽然目前Android开发中可能真正在使用MVVM的很少,但是是值得我们去做一些探讨和调研。
构建MVVM框架首先要具体了解各个模块的分工,接下来我们来讲解View,ViewModel,Model 的它们各自的职责所在。
关于协作,我们先来看下面的一张图:
上图反应了MVVM框架中各个模块的联系和数据流的走向,由上图可知View和Model 直接是解耦的,是没有直接联系的,也就是我之前说到的View 不做任何和业务逻辑和数据处理相关的事。我们从每个模块一一拆分来看。那么我们重点就是下面的三个协作。
- ViewModel与View的协作
ViewModel与Model的协作
ViewModel与ViewModel的协作
ViewModel与View的协作
图 2 中ViewModel 和View 是通过绑定的方式连接在一起的,绑定的一种是数据绑定,一种是命令绑定。数据的绑定 DataBinding 已经提供好了,简单的定义一些ObservableField就能把数据和控件绑定在一起了(如TextView的text属性),但是DataBinding框架提供的不够全面,比如说如何让一个URL绑定到一个ImageView让这个ImageView能自动去加载url指定的图片,如何把数据源和布局模板绑定到一个ListView,让ListView可以不需要去写Adapter和ViewHolder 相关的东西,而只是通过简单的绑定的方式把ViewModel的数据源绑定到Xml的控件里面就能快速的展示列表呢?这些就需要我们做一些工作和简单的封装。关于事件绑定也是一样,MVVM Light Toolkit 做了简单的封装,对于每个事件我们用一个ReplyCommand去处理就行了,ReplyCommand会把可能你需要的数据带给你,这使得我们处理事件的时候也只关心处理数据就行了。
由 图 1 中ViewModel的模块中我们可以看出ViewModel类下面一般包含下面5个部分:
我们先来看下示例代码,然后在一一讲解5个部分是干嘛用的:
//context
private Activity context;
//model(数据模型Bean)
private NewsService.News news;
private TopNewsService.News topNews;
//数据绑定(data field)
public final ObservableField imageUrl = new ObservableField<>();
public final ObservableField html = new ObservableField<>();
public final ObservableField title = new ObservableField<>();
// 一个变量包含了所有关于View Style 相关的字段
public final ViewStyle viewStyle = new ViewStyle();
//命令绑定(command)
public final ReplyCommand onRefreshCommand = new ReplyCommand<>(() -> {
})
public final ReplyCommand onLoadMoreCommand = new ReplyCommand<>((p) -> {
});
//Child ViewModel
public final ObservableList itemViewModel = new ObservableArrayList<>();
/** * ViewStyle 关于控件的一些属性和业务数据无关的Style 可以做一个包裹,这样代码比较美观,
ViewModel 页面也不会有太多的字段。 **/
public static class ViewStyle {
public final ObservableBoolean isRefreshing = new ObservableBoolean(true);
public final ObservableBoolean progressRefreshing = new ObservableBoolean(true);
}
- Context
Context 是干嘛用的呢,为什么每个ViewModel都最好需要持了一个Context的引用呢?ViewModel 不做和UI相关的事,不操作控件,也不更新UI,那为什么要有Context呢?原因主要有以下两点,当然也有其他用处,调用工具类、帮助类可能需要context参数等:
- Model
Model 是什么呢,其实就是数据原型,也就是我们用Json转过来的Java Bean,我们可能都知道,ViewModel要把数据映射到View中可能需要大量对Model的数据拷贝,拿Model 的字段去生成对应的ObservableField(我们不会直接拿Model的数据去做展示),这里其实是有必要在一个ViewModel 保留原始的Model引用,这对于我们是非常有用的,因为可能用户的某些操作和输入需要我们去改变数据源,可能我们需要把一个Bean 从列表页点击后传给详情页,可能我们需要把这个model 当做表单提交到服务器。这些都需要我们的ViewModel持有相应的model。
- Data Field (数据绑定)
Data Field 就是需要绑定到控件上的ObservableField字段, 无可厚非这是ViewModel的必须品。这个没有什么好说,但是这边有一个建议:
这些字段是可以稍微做一下分类和包裹的,比如说可能一些字段绑定到控件的一些Style属性上(如果说:长度,颜色,大小)这些根据业务逻辑的变化而动态去更改的,对于着一类针对View Style的的字段可以声明一个ViewStyle类包裹起来,这样整个代码逻辑会更清晰一些,不然ViewModel里面可能字段泛滥,不易管理和阅读性较差。而对于其他一些字段,比如说title,imageUrl,name这些属于数据源类型的字段,这些字段也叫数据字段,是和业务逻辑息息相关的,这些字段可以放在一块。
- Command (命令绑定)
Command (命令绑定)说白了就是对事件的处理(下拉刷新,加载更多,点击,滑动等事件处理),我们之前处理事件是拿到UI控件的引用,然后设置Listener,这些Listener 其实就是Command,但是考虑到在一个ViewModel 写各种Listener 并不美观,可能实现一个Listener就需要实现多个方法,但是我们可能只想要其中一个有用的方法实现就好了。同时实现Listener 会拿到UI的引用,可能会去做一些和UI相关的事情,这和我们之前说的ViewModel 不持有控件的引用,ViewModel不更改UI 有相悖。更重要一点是实现一个Listener 可能需要写一些UI逻辑才能最终获取我们想要的,简单一点的比如说,你想要监听ListView滑到最底部然后触发加载更多的事件,这时候你就要在ViewModel里面写一个OnScrollListener,然后在里面的onScroll方法中做计算,计算什么时候ListView滑动底部了,其实ViewModel的工作并不想去处理这些事件,它专注做的应该是业务逻辑和数据处理,如果有一个东西它不需要你自己去计算是否滑到底部,而是在滑动底部自动触发一个Command,同时把当前列表的总共的item数量返回给你,方便你通过 page=itemCount/LIMIT+1去计算出应该请求服务器哪一页的数据那该多好啊。MVVM Light Toolkit 帮你实现了这一点:
public final ReplyCommand onLoadMoreCommand = new ReplyCommand<>((itemCount) -> {
int page=itemCount/LIMIT+1;
loadData(page.LIMIT)
});
接着在XML 布局文件中通过bind:onLoadMoreCommand绑定上去就行了
当然Command并不是必须的,你完全可以依照你的习惯和喜好在ViewModel 写Listener,不过使用Command 可以使你的ViewModel 更简洁易读,你也可以自己定义更多的Command,自己定义其他功能Command,那么ViewModel的事件处理都是托管ReplyCommand来处理,这样的代码看起来会特别美观和清晰。
- Child ViewModel (子ViewModel)
子ViewModel 的概念就是在ViewModel 里面嵌套其他的ViewModel,这种场景还是很常见的。比如说你一个Activity里面有两个Fragment,ViewModel 是以业务划分的,两个Fragment做的业务不一样,自然是由两个ViewModel来处理,Activity 本身可能就有个ViewModel 来做它自己的业务,这时候Activity的这个ViewModel里面可能包含了两个Fragment分别的ViewModel。这就是嵌套的子ViewModel。还有另外一种就是对于AdapterView 如ListView RecyclerView,ViewPager等。
//Child ViewModelpublic final
ObservableList itemViewModel = new ObservableArrayList<>();
它们的每个Item 其实就对应于一个ViewModel,然后在当前的ViewModel 通过ObservableList持有引用(如上述代码),这也是很常见的嵌套的子ViewModel。我们其实还建议,如果一个页面业务非常复杂,不要把所有逻辑都写在一个ViewModel,可以把页面做业务划分,把不同的业务放到不同的ViewModel,然后整合到一个总的ViewModel,这样做起来可以使我们的代码业务清晰,简短意赅,也方便后人的维护。
总得来说ViewModel 和View 之前仅仅只有绑定的关系,View层需要的属性和事件处理都是在xml里面绑定好了,ViewModel层不会去操作UI,只会操作数据,ViewModel只是根据业务要求处理数据,这些数据自动映射到View层控件的属性上。关于ViewModel类中包含哪些模块和字段,这个需要开发者自己去衡量,这边建议ViewModel 不要引入太多的成员变量,成员变量最好只有上面的提到的5种(context、model、…),能不进入其他类型的变量就尽量不要引进来,太多的成员变量对于整个代码结构破坏很大,后面维护的人要时刻关心成员变量什么时候被初始化,什么时候被清掉,什么时候被赋值或者改变,一个细节不小心可能就出现潜在的Bug。太多不清晰定义的成员变量又没有注释的代码是很难维护的。
2016 8月25日更新:
我们会把UI控件的属性和事件都通过xml里面(如bind:text=@{…})绑定,但是如果一个业务逻辑要弹一个Dialog,但是你又不想在ViewModel里面做弹窗的事(ViewModel 不做UI相关的事)或者说改变ActionBar上面的图标的颜色,改变ActionBar按钮是否可点击,这些都不是写在xml里面(都是用java 初始化话),如何对这些控件的属性做绑定呢?我们先来看下代码:
public class MainViewModel implements ViewModel {
....
//true的时候弹出Dialog,false的时候关掉dialog
public final ObservableBoolean isShowDialog = new ObservableBoolean();
....
.....
}
// 在View层做一个对isShowDialog改变的监听
public class MainActivity extends RxBasePmsActivity {
private MainViewModel mainViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
.....
mainViewModel.isShowDialog.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
if (mainViewModel.isShowDialog.get()) {
dialog.show();
} else {
dialog.dismiss();
}
}
});
}
...
}
简单的说你可以对任意的ObservableField做监听,然后根据数据的变化做相应UI的改变,业务层ViewModel 只要根据业务处理数据就行,以数据来驱动UI。
//Model
private NewsDetail newsDetail;
private void loadData(long id) {
// Observable 用来获取网络数据
Observable> newsDetailOb =
RetrofitProvider.getInstance()
.create(NewsDetailService.class)
.getNewsDetail(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
// 将网络请求绑定到Activity 的生命周期
.compose(((ActivityLifecycleProvider) context).bindToLifecycle())
//变成 Notification 使我们更方便处理数据和错误
.materialize().share();
// 处理返回的数据
newsDetailOb.filter(Notification::isOnNext)
.map(n -> n.getValue())
// 给成员变量newsDetail 赋值,之前提到的5种变量类型中的一种(model类型)
.doOnNext(m -> newsDetail = m)
.subscribe(m -> initViewModelField(m));
// 网络请求错误处理
NewsListHelper.dealWithResponseError(
newsDetailOb.filter(Notification::isOnError)
.map(n -> n.getThrowable()));
}
//Model -->ViewModel
private void initViewModelField(NewsDetail newsDetail) {
viewStyle.isRefreshing.set(false);
imageUrl.set(newsDetail.getImage());
Observable.just(newsDetail.getBody())
.map(s -> s + "")
.subscribe(s -> html.set(s));
title.set(newsDetail.getTitle());
}
以上代码基本把注释补全了,基本思路比较清晰,,Rxjava涉及的操作符都是比较基本的,如有不懂,可以稍微去入门,之后的源码里面ViewModel数据逻辑处理都是用Rxjava做,所以需要提前学习一下方便你看懂源码。
注:我们推荐使用MVVM 和 RxJava一块使用,虽然两者皆有观察者模式的概念,但是我们RxJava不使用在针对View的监听,更多是业务数据流的转换和处理。DataBinding框架其实是专用于View-ViewModel的动态绑定的,它使得我们的ViewModel 只需要关注数据,而RxJava 提供的强大数据流转换函数刚好可以用来处理ViewModel中的种种数据,得到很好的用武之地,同时加上Lambda表达式结合的链式编程,使ViewModel 的代码非常简洁同时易读易懂。
FragmentViewModel 获取消息后通知MainViewModel 并把数据传给它:
combineRequestOb.filter(Notification::isOnNext)
.map(n -> n.getValue())
.map(p -> p.first)
.filter(m -> !m.getTop_stories().isEmpty())
.doOnNext(m ->Observable.just(NewsListHelper.isTomorrow(date)).filter(b -> b).subscribe(b -> itemViewModel.clear()))
// 上面的代码可以不看,就是获取网络数据 ,通过send把数据传过去
.subscribe(m -> Messenger.getDefault().send(m, TOKEN_TOP_NEWS_FINISH));
MainViewModel 接收消息并处理:
Messenger.getDefault().register(activity, NewsViewModel.TOKEN_TOP_NEWS_FINISH, TopNewsService.News.class, (news) -> {
// to something....
}
在MainActivity onDestroy 取消注册就行了(不然导致内存泄露)
@Override
protected void onDestroy() {
super.onDestroy();
Messenger.getDefault().unregister(this);
}
当然上面的例子也只是简单的说明下,Messenger可以用在很多场景,通知,广播都可以,不一定要传数据,在一定条件下也可以用在View层和ViewModel 上的通信和广播。运用范围特别广,需要开发者结合实际的业务中去做更深层次的挖掘。
本篇博文讲解主要是一些个人开发过程中总结的Android MVVM构建思想,更多是理论上各个模块如何分工,代码如何设计,虽然现在业界使用Android MVVM模式开发还比较少,但是随着DataBinding 1.0 的发布,相信在Android MVVM 这块领域会更多的人来尝试,刚好最近用MVVM开发了一段时间,有点心得,写出来仅供参考。
文中讲解的过程代码比较少,代码用到了自己开发的一个MVVM Light Toolkit 库,而且还是RxJava + Lambda 的代码,估计很多人看着都晕菜了,这边会把源码公布出来。如果你还没有尝试过用RxJava+Retrofit+DataBinding 构建Android MVVM 应用程序,那么你可以试着看一下这边的源码并且做一下尝试,说不定你会喜欢上这样的开发框架。
关于MVVM Light Toolkit 只是一个工具库,主要目的是更快捷方便的构建Android MVVM应用程序,在里面添加了一些控件额外属性和做了一些事件的封装,同时引进了全局消息通道Messenger,用起来确实非常方便,你可以尝试一下,当然还有不少地方没有完善和优化,后续也会不断更新和优化,如果不能达到你的业务需求时,你也可以自己添加自己需要的属性和事件。
源码地址 https://github.com/Kelin-Hong/MVVMLight
library —> library是MVVM Light Toolkit 的源码,源码很简单,感兴趣的同学可以看看,没什么多少的技术难度,可以根据自己的需求,添加更多的控件的属性和事件绑定。
sample —> 本文涉及的代码均处出于这个项目,sample 一个知乎日报的App的简单实现,代码包含了一大部分 MVVM Light Toolkit 的使用场景,(Data、Command、Messenger均有涉及),同时sample严格按照博文阐述的MVVM的设计思想开发的,对理解本文有很大的帮助,欢迎clone下来看看。
Sample 截图
源码涉及 RxJava+Retrofit+Lambda 如有不懂或没接触过,花点时间入门一下,用到都是比较简单的东西。
希望这篇博客在如何构建Android MVVM应用程序对你有所帮助,如有任何疑问,可以给我留言,欢迎大家共同探讨,如果对MVVM Light Toolkit 有任何问题,也可以反馈给我。
我呢也将自己当前所在技术领域的各项知识点、工具、框架等汇总成一份技术路线图,和一些架构进阶视频、全套学习PDF文件、面试文档、源码笔记
Android学习PDF+架构视频+面试文档+源码笔记
如果你有其他需要的话,也可以在 GitHub 上查看,Android进阶的资料也会陆续上传到Github上的 。
330页PDFA MVC、MVP、MVVM 的核心知识点
好了,以上便是今天的分享,希望为各位 GitHubDaily 水友后续的学习提供方便。觉得内容不错,也欢迎多多转发分享给身边的朋友哈。