初探安卓MVVM框架设计#
一. 什么是MVVM?
MVVM是近几年流行的一种设计框架,基于该框架设计的应用程序具有良好的解耦和可扩展性,大幅降低了维护成本,提高了程序员的开发效率.在了解MVVM框架之前,我们有必要回顾一下其他设计框架.
1. MVC模式
MVC模式的意思是,软件可以分成三个部分.
视图(View):用户界面
控制器(Controller):业务逻辑
模型(Model):数据保存
各部分之间的通信方式如下.
1.View传送指令到Controller
2.Controller完成业务逻辑后,要求Model改变状态
3.Model将新的数据发送到View,用户得到反馈
所有通信都是单向的.我们传统的Android开发都是基于这种模式.每一层可以代表我们常用的如下组件:
Model层: sqlite数据库, JavaBean, SharedPreference, sdcard,获取网络数据的api等
View层: xml布局文件,自定义控件等
Controller层: Activity等
此处需要注意的是,在传统的MVC设计模式中,
Activity属于Controller层而不是View层,因为Activity即承担了数据调用,也承担了界面展示,相当于View和Model中间的协调器.很多初学者都会误认为Activity属于View层.当然,这种说法仅限用MVC模式,换做其他模式就不一定了哦!
2. MVP模式
MVC模式普及了一段时间之后,逐渐暴露出一些问题.比如我们发现,
Activity中写的代码太多,有时候一个Activity甚至达到了四五千行代码,维护起来极为不便.原因也很明显,就是Activity既参与api访问和数据调用,又参与了界面的更新,职能划分不明确,没有完全实现解耦.我们的想法是,能不能让Activity只做界面响应和更新,其他业务逻辑全部由另外一个单独模块来完成?于是MVP诞生了.
MVP模式将Controller改名为Presenter,同时改变了通信方向.
1.各部分之间的通信,都是双向的.
2.View与Model不发生联系,都通过Presenter传递.
3.View非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而Presenter非常厚,所有逻辑都部署在那里.
当这样调整了之后, Activity就纯粹属于View层了,所有业务逻辑全由Presenter来完成.当View界面被用户操作时(比如按钮点击), View层就会调用Presenter完成相关业务逻辑,而Presenter完成了之后,就会将结果以回调的形式传递给View层,由View层完成界面刷新.具体代码如何实现我就不多说了,因为我们今天的重点是MVVM,如果有兴趣研究的话可以在网上搜索MVP相关的例子程序,我也找了一个,仅供参考:
http://blog.csdn.net/vector_yi/article/details/24719873
3. MVVM模式
当我们采用MVP模式之后,发现Activity几乎没啥事可做了,我们的项目代码层级也清晰了,也好维护了.但是MVP也有缺点,比如,为了实现MVP,我们需要额外增加好多接口和类,比如,一个Activity需要对应一个Presenter类和Presenter接口,同时为了方便Activity和Presenter进行通信,还得再定义一个回调接口IView,也就是说,每一个Activity都需要额外增加两个接口和一个类,无疑提高了代码量.而MVVM的诞生,就解决了这个问题!
MVVM模式将Presenter改名为ViewModel,基本上与MVP模式完全一致.
唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在ViewModel,反之亦然.
有没有注意到, MVVM和MVP几乎是一样的,唯一的不同就在于View和ViewModel之间的那根线, MVP是两根,表示View调用Presenter执行逻辑,Presenter调用View来返回数据,更新界面;MVVM中只有一根线两个箭头,代表的是View和ViewModel双向绑定,自动同步数据,无需手动调用相关方法进行通信,从而减少了代码量.而这种双向绑定的机制,都归功于谷歌推出的DataBinding的新功能.下面我们来研究一下到底什么是DataBinding.
二. 使用DataBinding构建MVVM框架
2.1 什么是DataBinding
2015 Google IO大会带来的DataBinding库使得Android开发者可以方便的实现MVVM架构模式.使用DataBinding可以改善应用程序的开发,使代码更加干净优雅.
DataBinding的使用教程在网上已经很多了,我在这里只是简单提一下最基本的用法,大家体验一下就好.如果想更深入学习的话,建议查看谷歌官方文档:https://developer.android.com/topic/libraries/data-binding/index.html
2.2 DataBinding环境配置
1.由于新版Android Studio已经内置了DataBinding的功能,为了方便开发,请确保使用AndroidStudio 1.3及以上的版本.
2.在app的build.gradle文件中添加下面的内容:
android {
....
dataBinding {
enabled =true
}
}
3.重新编译项目,配置完成.
2.3 DataBinding的基本使用
1.布局文件
根标签使用layout,在layout标签下用data标签来配置数据,例子如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="cn.itcast.mvvmdemo.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstname}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastname}"/>
</LinearLayout>
</layout>
<variable name="user" type="cn.itcast.mvvmdemo.User"/>
这句话代表,声明了一个user变量,类型是cn.itcast.mvvmdemo.User,当然这个User要提前定义.
public class User {
private String firstname;
private String lastname;
public User(String firstname, Stringlastname) {
this.firstname = firstname;
this.lastname = lastname;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(Stringfirstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(Stringlastname) {
this.lastname = lastname;
}
}
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstname}"/>
控件布局写法和以前一样,唯一不同之处在于控件内容的赋值部分.以前我们都会写一个默认值,然后再在代码中动态修改控件的值.此时已经不需要了. @{user.firstname}代表当前TextView的值取自于user对象中的firstname字段.
2. Activity代码
public class MainActivity extends AppCompatActivity {
private User user;
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding =DataBindingUtil.setContentView(this, R.layout.activity_main);
user = new User("尼古拉斯凯奇", "赵四");
binding.setUser(user);
}
}
ActivityMainBinding是DataBinding自动根据布局文件生成的类,不需要手动创建.该类的命名方式取自于布局文件的名称.比如布局文件名叫activity_main,那么生成的类名就叫ActivityMainBinding.
当使用DataBinding时,需要用DataBindingUtil来设置Activity的布局.
binding.setUser(user);表示将user对象和布局文件绑定在了一起,
user对象的所有属性值都可以同步映射到布局文件的控件中.
3. 运行效果
你会发现,我们没有像往常那样在activity中findViewById,找到控件后给动态赋值,而是通过DataBinding的方式直接将对象的值作用在了布局文件中,从而使我们的代码更加优雅和简洁.
2.3 DataBinding响应点击事件
1.首先,写一个事件处理器MyHandler
public class MyHandler {
public void onButtonClick(View view){
System.out.println("按钮被点击了");
}
}
这是一个普通的类,在onButtonClick中处理按钮点击后应该执行的操作.
2.在之前布局文件的基础上,添加一个按钮
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="cn.itcast.mvvmdemo.User"/>
<variable
name="handler"
type="cn.itcast.mvvmdemo.MyHandler"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstname}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastname}"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{handler.onButtonClick}"
android:text="点击我"
/>
</LinearLayout>
</layout>
在data中声明handler,类型是MyHandler,在Button的onClick中定义要执行的操作.
android:onClick="@{handler.onButtonClick}"
3.在Activity中,将MyHandler设置给Binding对象.
binding.setHandler(new MyHandler());
4.运行后看效果
2.4 数据变化后同步更新界面
点击按钮之后,我们想修改一下firstname和lastname的值,然后更新界面.如果采用DataBinding的话,我们会怎么做?
1.将用户对象传递给MyHandler
public class MyHandler {
private User user;
public MyHandler(User user) {
this.user = user;
}
public void onButtonClick(View view){
System.out.println("按钮被点击了");
user.setFirstname("蒙拉丽莎");
user.setLastname("鸭蛋");
}
}
在按钮点击的时候,修改了user的firstname和lastname.如果放在往常,你肯定就立马想找到那两个TextView对象来重新设置数据,而现在,你什么都不用做,只要数据变了,界面就会立即同步更新.有这么神奇?其实你得提前做好准备,才会有这样的效果.
2.我们需要把User类调整一下:
public class User extends BaseObservable {
private String firstname;
private String lastname;
public User(String firstname, Stringlastname) {
this.firstname = firstname;
this.lastname = lastname;
}
@Bindable
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
notifyPropertyChanged(BR.firstname);
}
@Bindable
public String getLastname() {
return lastname;
}
public void setLastname(String lastname){
this.lastname = lastname;
notifyPropertyChanged(BR.lastname);
}
}
在getFirstname和getLastname两个方法中加注解@Bindable,这样的话DataBinding会自动在BR文件中生成这两个字段的id. BR文件类似于R文件,是DataBinding特有的用于维护id的一个文件. BR文件由编译器自动生成.
在setFirstname和setLastname的方法中添加notifyPropertyChanged方法,同时将你要更新的字段id传递过去.此方法用于通知系统数据已经变化,需要更新界面.
3.我们案例的最终项目结构如下图所示:
三. 总结
在学习MVVM框架时我一直有一个纠结:MVC和MVP结构很清晰,我很容易能分清楚哪个组件属于哪个模块,但到了MVVM我就有点晕了,因为网上所有介绍MVVM的文章几乎都指向了DataBinding,并没有讲到具体每一层对应哪些组件.目前就我的初步了解,我大概会这么划分:
View层: xml, Activity,自定义控件等;
Model层: sqlite数据库, JavaBean,SharedPreference, sdcard,获取网络数据的api等
ViewModel层:独立的业务逻辑处理模块,部分参与业务逻辑的JavaBean
在我们的例子项目中,MainActivity, activity_main.xml属于View层; User属于Model层; MyHandler属于ViewModle层.
不过后来我又想了一下,我们真有必要划分清楚谁是View,谁是ViewModel,谁是Model吗?程序设计本来就很复杂,难免会碰到一些模棱两可的模块,各个层都参与一下,但又不属于任何一层.我们开发应用程序是为了实现功能,我们进行框架设计是为了提高扩展性并降低维护成本,在这种大前提下,我们的细节如何处理就已经无关紧要了.事实上,当你采用了DataBinding来构建你的程序时,你其实就已经在用MVVM框架了.
当然DataBinding的用法还有很多,此文介绍的只是冰山一角,比如如何在ListView和RecyclerView中使用DataBinding,布局文件中关于DataBinding的高级用法等等,此文都没有提及.如果你想了解更多,就请关注官方文档.
关于MVVM和DataBinding的资料和博客,网上已经有很多了,由于MVVM内容确实繁杂,所以网上的文章没有特别全面的,侧重点都有所不同.当然,此文是从另一个角度来重新解读了一下MVVM模式,如果能从此文中获取对你有益的内容,会让我倍感欣慰.
Demo附件下载链接: http://pan.baidu.com/s/1pLligyf