初探安卓MVVM框架设计

作者: Kevin1205 | 来源:发表于2016-11-14 12:54 被阅读2014次

    初探安卓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

    相关文章

      网友评论

      • 老实李:你好,我想问下,像侧滑菜单的headLayout中的数据怎么绑定呢?
      • 黑马有点白986:感谢作者分享,辛苦了~~
      • 布鲁马:老师,用马克飞象软件编辑MD,然后粘贴到gitbook。简书,CSDN这样很方便的。
        Kevin1205:@布鲁马 好的, 多谢了. 回头我试一下
      • 刘洋浪子:老师 你csdn账号哪一个哟 ?
        Kevin1205:@刘洋浪子 欢迎访问: http://blog.csdn.net/yinkai1205
      • f13ab180e7fd:老师,第一时间来关注你,希望以后会有更多的干货
        Kevin1205:@下半场 多谢支持! 以前在csdn上发东西, 最近觉得简书更好使, 以后估计就在这里发了 :smile:

      本文标题:初探安卓MVVM框架设计

      本文链接:https://www.haomeiwen.com/subject/ayshpttx.html