美文网首页Android developerAndroid Android
如何构建Android MVVM应用程序

如何构建Android MVVM应用程序

作者: Kelin | 来源:发表于2016-06-15 14:31 被阅读46307次

    1、概述

    Databinding 是一种框架,MVVM是一种模式,两者的概念是不一样的。我的理解DataBinding是一个实现数据和UI绑定的框架,只是一个实现MVVM模式的工具。ViewModel和View可以通过DataBinding来实现单向绑定和双向绑定,这套UI和数据之间的动态监听和动态更新的框架Google已经帮我们做好了。在MVVM模式中ViewModel和View是用绑定关系来实现的,所以有了DataBinding 使我们构建Android MVVM 应用程序成为可能。
      之前看了很多关于DataBinding的博客和相关的一些Demo,大多数就是往xml布局文件传入一些数据,然后把这些数据绑定到控件上( 如TextView binding:text=“@{user.name} ),接着在这些控件上(如Button binding:setOnClickListener="@{user.listener}") 设置一些事件到控件上,基本讲述都是DataBinding的基本用法。但是并没有人告诉你把一个onClickListener 写到一个类并把这个listener绑定到xml里面上是不是不太好,也没有人告诉你这个和xml布局绑定的ViewModel类应该放哪些数据,应该做什么事?应该如何设计?更是很少有博文来告诉你在Android 中如何通过Data Binding 去构建MVVM 的应用框架。这也就是是本篇文章的重点。接下来,我们先来看看什么是MVVM,然后在一步一步来设计整个应用程序框架。

    源码地址 https://github.com/Kelin-Hong/MVVMLight

    2、MVC、MVP、MVVM

    首先,我们先大致了解Android开发中常见的模式,以便我们更深入了解MVVM 模式。

    • MVC

    View:对应于xml布局文件
    Model:实体模型
    Controllor:对应于Activity业务逻辑,数据处理和UI处理

    从上面看起来各个组件的职责视乎还挺耦合MVC的,但是打开Android的一个Activity文件,一看一言难尽, Android中经常会出现数千行的Activity代码,究其原因,Android中纯粹作为View的各个XML视图功能太弱,Activity基本上都是View和Controller的合体,既要负责视图的显示又要加入控制逻辑,承担的功能过多,代码量大也就不足为奇。所有更贴切的目前常规的开发说应该是View-Model 模式,大部分都是通过Activity的协调,连接,和处理逻辑的。

    • MVP

    **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层太大,代码臃肿的问题。

    • MVVM

    **View: **对应于Activity和xml,负责View的绘制以及与用户交互
    **Model: **实体模型
    **ViewModel: **负责完成View于Model间的交互,负责业务逻辑

    MVVM的目标和思想MVP类似,利用数据绑定(Data Binding)、依赖属性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一个更加灵活高效的架构。

    • 数据驱动
      在MVVM中,以前开发模式中必须先处理业务数据,然后根据的数据变化,去获取UI的引用然后更新UI,通过也是通过UI来获取用户输入,而在MVVM中,数据和业务逻辑处于一个独立的View Model中,ViewModel只要关注数据和业务逻辑,不需要和UI或者控件打交道。由数据自动去驱动UI去自动更新UI,UI的改变又同时自动反馈到数据,数据成为主导因素,这样使得在业务逻辑处理只要关心数据,方便而且简单很多。
    • 低耦合度
      MVVM模式中,数据是独立于UI的,ViewModel只负责处理和提供数据,UI想怎么处理数据都由UI自己决定,ViewModel 不涉及任何和UI相关的事也不持有UI控件的引用,即使控件改变(TextView 换成 EditText)ViewModel 几乎不需要更改任何代码,专注自己的数据处理就可以了,如果是MVP遇到UI更改,就可能需要改变获取UI的方式,改变更新UI的接口,改变从UI上获取输入的代码,可能还需要更改访问UI对象的属性代码等等。
    • 更新 UI
      在MVVM中,我们可以在工作线程中直接修改View Model的数据(只要数据是线程安全的),剩下的数据绑定框架帮你搞定,很多事情都不需要你去关心。
    • 团队协作
      MVVM的分工是非常明显的,由于View和View Model之间是松散耦合的。一个是处理业务和数据,一个是专门的UI处理。完全有两个人分工来做,一个做UI(xml 和 Activity)一个写ViewModel,效率更高。
    • 可复用性
      一个View Model复用到多个View中,同样的一份数据,用不同的UI去做展示,对于版本迭代频繁的UI改动,只要更换View层就行,对于如果想在UI上的做AbTest 更是方便的多。
    • 单元测试
      View Model里面是数据和业务逻辑,View中关注的是UI,这样的做测试是很方便的,完全没有彼此的依赖,不管是UI的单元测试还是业务逻辑的单元测试,都是低耦合的。

    通过上面对MVVM的简述和其他两种模式的对比,我们发现MVVM对比MVC和MVP来说还是存在比较大的优势,虽然目前Android开发中可能真正在使用MVVM的很少,但是是值得我们去做一些探讨和调研。

    3、如何构建MVVM应用程序

    1. 如何分工

    构建MVVM框架首先要具体了解各个模块的分工,接下来我们来讲解View,ViewModel,Model 的它们各自的职责所在。

    • View
      View层做的就是和UI相关的工作,我们只在XML和Activity或Fragment写View层的代码,View层不做和业务相关的事,也就是我们的Activity 不写和业务逻辑相关代码,也不写需要根据业务逻辑来更新UI的代码,因为更新UI通过Binding实现,更新UI在ViewModel里面做(更新绑定的数据源即可),Activity 要做的事就是初始化一些控件(如控件的颜色,添加 RecyclerView 的分割线),Activity可以更新UI,但是更新的UI必须和业务逻辑和数据是没有关系的,只是单纯的根据点击或者滑动等事件更新UI(如 根据滑动颜色渐变、根据点击隐藏等单纯UI逻辑),Activity(View层)是可以处理UI事件,但是处理的只是处理UI自己的事情,View层只处理View层的事。简单的说:View层不做任何业务逻辑、不涉及操作数据、不处理数据、UI和数据严格的分开。
    • ViewModel
      ViewModel层做的事情刚好和View层相反,ViewModel 只做和业务逻辑和业务数据相关的事,不做任何和UI、控件相关的事,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,操作的也都是对数据进行操作,这些个数据源绑定在相应的控件上会自动去更改UI,开发者不需要关心更新UI的事情。DataBinding 框架已经支持双向绑定,这使得我们在可以通过双向绑定获取View层反馈给ViewModel层的数据,并进行操作。关于对UI控件事件的处理,我们也希望能把这些事件处理绑定到控件上,并把这些事件统一化,方便ViewModel对事件的处理和代码的美观。为此我们通过BindingAdapter 对一些常用的事件做了封装,把一个个事件封装成一个个Command,对于每个事件我们用一个ReplyCommand<T>去处理就行了,ReplyCommand<T>会把可能你需要的数据带给你,这使得我们处理事件的时候也只关心处理数据就行了,具体见MVVM Light Toolkit 使用指南的 Command 部分。再强调一遍ViewModel 不做和UI相关的事。
    • **Model **
      Model 的职责很简单,基本就是实体模型(Bean)同时包括Retrofit 的Service ,ViewModel 可以根据Model 获取一个Bean的Observable<Bean>( RxJava ),然后做一些数据转换操作和映射到ViewModel 中的一些字段,最后把这些字段绑定到View层上。

    2. 如何协作

    关于协作,我们先来看下面的一张图:

    图 1

    上图反应了MVVM框架中各个模块的联系和数据流的走向,由上图可知View和Model 直接是解耦的,是没有直接联系的,也就是我之前说到的View 不做任何和业务逻辑和数据处理相关的事。我们从每个模块一一拆分来看。那么我们重点就是下面的三个协作。

    • ViewModel与View的协作
    • ViewModel与Model的协作

    • ViewModel与ViewModel的协作

    • ViewModel与View的协作

    图 2

    图 2 中ViewModel 和View 是通过绑定的方式连接在一起的,绑定的一种是数据绑定,一种是命令绑定。数据的绑定 DataBinding 已经提供好了,简单的定义一些ObservableField就能把数据和控件绑定在一起了(如TextView的text属性),但是DataBinding框架提供的不够全面,比如说如何让一个URL绑定到一个ImageView让这个ImageView能自动去加载url指定的图片,如何把数据源和布局模板绑定到一个ListView,让ListView可以不需要去写Adapter和ViewHolder 相关的东西,而只是通过简单的绑定的方式把ViewModel的数据源绑定到Xml的控件里面就能快速的展示列表呢?这些就需要我们做一些工作和简单的封装。MVVM Light Toolkit 已经帮我们做了一部分的工作,详情可以查看MVVM Light Toolkit 使用指南。关于事件绑定也是一样,MVVM Light Toolkit 做了简单的封装,对于每个事件我们用一个ReplyCommand<T>去处理就行了,ReplyCommand<T>会把可能你需要的数据带给你,这使得我们处理事件的时候也只关心处理数据就行了。

    图 1 中ViewModel的模块中我们可以看出ViewModel类下面一般包含下面5个部分:

    • Context (上下文)
    • Model (数据模型Bean)
    • Data Field (数据绑定)
    • Command (命令绑定)
    • Child ViewModel (子ViewModel)

    我们先来看下示例代码,然后在一一讲解5个部分是干嘛用的:

    //context
    private Activity context;
    
    //model(数据模型Bean)
    private NewsService.News news;
    private TopNewsService.News topNews;
    
    //数据绑定(data field)
    public final ObservableField<String> imageUrl = new ObservableField<>();
    public final ObservableField<String> html = new ObservableField<>();
    public final ObservableField<String> title = new ObservableField<>();
    // 一个变量包含了所有关于View Style 相关的字段
    public final ViewStyle viewStyle = new ViewStyle();
    
    
    //命令绑定(command)
    public final ReplyCommand onRefreshCommand = new ReplyCommand<>(() -> {    
    
    })
    public final ReplyCommand<Integer> onLoadMoreCommand = new ReplyCommand<>((p) -> { 
    
    });
    
    
    //Child ViewModel
    public final ObservableList<NewItemViewModel> 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参数等:
    • 通过图1中,我们发现ViewModel 通过传参给Model 然后得到一个Observable<Bean>,其实这就是网络请求部分,做网络请求我们必须把Retrofit Service返回的Observable<Bean>绑定到Context的生命周期上,防止在请求回来时Activity已经销毁等异常,其实这个Context的目的就是把网络请求绑定到当前页面的生命周期中。
    • 在图1中,我们可以看到两个ViewModel 之间的联系是通过Messenger来做,这个Messenger 是需要用到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<Integer> onLoadMoreCommand =  new ReplyCommand<>((itemCount) -> { 
       int page=itemCount/LIMIT+1; 
       loadData(page.LIMIT)
    });
    

    接着在XML 布局文件中通过bind:onLoadMoreCommand绑定上去就行了

    <android.support.v7.widget.RecyclerView 
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     bind:onLoadMoreCommand="@{viewModel.loadMoreCommand}"/>
    

    具体想了解更多请查看 MVVM Light Toolkit 使用指南,里面有比较详细的讲解Command的使用。当然Command并不是必须的,你完全可以依照你的习惯和喜好在ViewModel 写Listener,不过使用Command 可以使你的ViewModel 更简洁易读,你也可以自己定义更多的Command,自己定义其他功能Command,那么ViewModel的事件处理都是托管ReplyCommand<T>来处理,这样的代码看起来会特别美观和清晰。

    • 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> itemViewModel = new ObservableArrayList<>();
    

    它们的每个Item 其实就对应于一个ViewModel,然后在当前的ViewModel 通过ObservableList<ItemViewModel>持有引用(如上述代码),这也是很常见的嵌套的子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。

    • ViewModel与Model的协作 
      从图1 中,Model 是通过Retrofit 去获取网络数据的,返回的数据是一个Observable<Bean>( RxJava ),Model 层其实做的就是这些。那么ViewModel 做的就是通过传参数到Model层获取到网络数据(数据库同理)然后把Model的部分数据映射到ViewModel的一些字段(ObservableField),并在ViewModel 保留这个Model的引用,我们来看下这一块的大致代码(代码涉及到简单RxJava,如看不懂可以查阅入门一下):
     //Model
     private NewsDetail newsDetail;
     
     private void loadData(long id) {  
       //  Observable<Bean> 用来获取网络数据
       Observable<Notification<NewsDetailService.NewsDetail>>   newsDetailOb =   
       RetrofitProvider.getInstance()
                      .create(NewsDetailService.class)   
                      .getNewsDetail(id)                   
                      .subscribeOn(Schedulers.io())      
                      .observeOn(AndroidSchedulers.mainThread())
                     // 将网络请求绑定到Activity 的生命周期
                      .compose(((ActivityLifecycleProvider) context).bindToLifecycle()) 
                     //变成 Notification<Bean> 使我们更方便处理数据和错误
                      .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 + "<style type=\"text/css\">" + newsDetail.getCssStr())           
                .map(s -> s + "</style>")            
                .subscribe(s -> html.set(s));   
         title.set(newsDetail.getTitle());
     }
    

    以上代码基本把注释补全了,基本思路比较清晰,,Rxjava涉及的操作符都是比较基本的,如有不懂,可以稍微去入门,之后的源码里面ViewModel数据逻辑处理都是用Rxjava做,所以需要提前学习一下方便你看懂源码。

    注:我们推荐使用MVVM 和 RxJava一块使用,虽然两者皆有观察者模式的概念,但是我们RxJava不使用在针对View的监听,更多是业务数据流的转换和处理。DataBinding框架其实是专用于View-ViewModel的动态绑定的,它使得我们的ViewModel 只需要关注数据,而RxJava 提供的强大数据流转换函数刚好可以用来处理ViewModel中的种种数据,得到很好的用武之地,同时加上Lambda表达式结合的链式编程,使ViewModel 的代码非常简洁同时易读易懂。

    • ViewModel与ViewModel的协作 
      在图 1 中 我们看到两个ViewModel 之间用一条虚线连接着,中间写着Messenger,Messenger 可以理解是一个全局消息通道,引入messenger最主要的目的就实现ViewModel和ViewModel的通信,也可以用做View和ViewModel的通信,但是并不推荐这样做。ViewModel主要是用来处理业务和数据的,每个ViewModel都有相应的业务职责,但是在业务复杂的情况下,可能存在交叉业务,这时候就需要ViewModel和ViewModel交换数据和通信,这时候一个全局的消息通道就很重要的。关于Messenger 的详细使用方法可以参照 MVVM Light Toolkit 使用指南的 Messenger 部分,这边给出一个简单的例子仅供参考:
      场景是这样的,你的MainActivity对应一个MainViewModel,MainActivity 里面除了自己的内容还包含一个Fragment,这个Fragment 的业务处理对应于一个FragmentViewModel,FragmentViewModel请求服务器并获取数据,刚好这个数据MainViewModel也需要用到,我们不可能在MainViewModel重新请求数据,这样不太合理,这时候就需要把数据传给MainViewModel,那么应该怎么传,彼此没有引用或者回调。那么只能通过全局的消息通道Messenger。

    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 上的通信和广播。运用范围特别广,需要开发者结合实际的业务中去做更深层次的挖掘。

    4、总结和源码###

    • 本篇博文讲解主要是一些个人开发过程中总结的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,用起来确实非常方便,你可以尝试一下,当然还有不少地方没有完善和优化,后续也会不断更新和优化,如果不能达到你的业务需求时,你也可以自己添加自己需要的属性和事件。如果想更深入了解MVVM Light Toolkit 请看我这篇博文 MVVM Light Toolkit 使用指南

    • 源码地址 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 有任何问题,也可以反馈给我。

    相关文章

      网友评论

      • 技术纠结者:博主,不知道你和这篇文章作者是否为同一个人(林凯)(https://tech.meituan.com/android_mvvm.html)。请教个问题,文章上说model层最好是独立抽离出来的,它提供存储数据的接口(或者通过网络请求接口,返回obserable给调用者),那么model是否会存储这个obserable或者某些公共的数据(多个vm需要依赖的数据)。我这有个需求,比如 某个fm上操作了个添加商品,多个fm中需要作出相关的不同显示。那么我这个购物车数据是否存放在model中,如果是那么多个fm中的vm是如何得到相应更新的呢。本人思路在vm观察这个公共model的某数据(购物车列表),当这个数据发生变化的时候,vm重新修改vm中的m,从而更新ui,我这样做是否合理?
      • 宿罪:楼主,按照你对MVVM的设计思想,就是给RecyclerView添加适配器的时候通过items和ItemView来实现,这种方式不用写Adapter,而是通过嵌套子ViewMode,也就是 ItemViewMode。但是,这种方式我遇到一个瓶颈就是RecyclerView的Item布局比较复杂,我需要做的事情比如item播放视频,通过绑定事件Command的方式是可以响应Item的点击事件,但是呢,我需要做不仅是响应这个事件,而且需要响应这个事件后改变item layout中其他控件的状态,但是呢,command没有其他控件的引用,所以改变不了。于是,我就改用Adapter来实现了,这样Adapter可以有其他View的引用,可以更改,不知道楼主,你是怎么解决这种问题的(通过嵌套ViewMode方式item单纯地显示数据是没问题,但是item之间的view怎么交互呢?)
      • bb07fdf8d02e:👍,受益匪浅,谢谢楼主
      • 控阁:写的很好,必须点个赞..
      • 一大条咸鱼:学习大佬代码
      • 磐龍:如果activity_user.xml这个xml布局(命名为A),又include了另外一个layout(暂时命名为B),B我们在A布局声明了UserViewModel,但是B布局某个TextView控件也需要用到UserViewModel的数据,要怎么做才是比较合理的呢?
      • 121e9d9f5d8e:mvvm架构也有点缺点,如果需要替换view中的ui view层的东西,由于xml布局中已经存在大量的binding事件。到时ui的复用不是很好
        Valenhua:ui复用应该是代码和布局一起封装成组件,这样就没问题的了
      • ghzhang:这么久没有更新版本了,请问你现在的项目还在要用吗?有没遇到重大问题。
      • 2ea2e01d3ff7:为什么感觉看你的写的东西,有种费劲的感觉,明明可以有很好的表达,可总要看很久才知道哦原来是讲1+1=2
        丿灬Joker:这就是国人与国外的编程思想区别,国内简单粗暴,越用越残,国外呵呵,博主,请收下我的膜拜!
      • 番茄And鸡蛋:内存泄漏严重怎么办,越用越卡。没有及时回收内存,每次创建新的视图都会增加内存占用,销毁后不知道是哪里泄漏了,难道是创建的数据过多?每次都是new 的新viewModel,求解。。。
      • 搏击爱好者:MVVM Light Toolkit 什么时候更新下个版本?
        Kelin:我自己项目也用着MVVM Light Toolkit,平时在使用中自己修了点小东西,没有重大更新,也就没有再更新个小版本。回头我整理下看看:smile:
      • 故事细腻言不由己:楼主请问我用上拉加载,没反应,有什么需要注意的吗
        Kelin:MVVM Light Toolkit 回复你了
      • 35c4ddd83e3c:写的很棒
      • 679a3d031c4d:我想问下@bindingadapter自定义出来的属性具体是在activity的哪一个生命周期里边执行!?

        因为有一个问题就是我在给ViewPager设置Indicator的时候 binding.circlePageIndicator.setViewPager(binding.viewPager);
        报错误viewPager没有设置adapter.


        @BindingAdapter(value = {"views"}, requireAll = false)
        public static void init(final ViewPager viewPager, List<View> items) {
        if (items == null) {
        items = new ArrayList<>();
        }
        ViewPagerAdapter adatper;
        adatper = new ViewPagerAdapter(items);
        viewPager.setAdapter(adatper);
        }
        679a3d031c4d:@Kelin 搜嘎
        Kelin:绑定的字段在控件初始化完成后就会去做,可以理解在onCreate()的时候就会去做。recyclerview的adapter 应该是在列表有数据的时候才创建,所有你在往viewmodelList 加完数据之后 binding.circlePageIndicator.setViewPager(binding.viewPager); 就不会有问题了:smile:
      • fd93bb4b8ebc:楼主,请问mvvm足够满足日常的开发吗。我怕不够成熟,以后遇到重大bug无法解决就完蛋了
      • faa66766e787:https://github.com/Kelin-Hong/MVVMLight这个项目导入到android studio中不能编译,有个library和sample,要怎么弄,新手,谢谢了
        Kelin:@沙漏_3f2e 你看下gradle 相关和插件的版本能对于上吗?,根据具体报错的信息找找
      • hello_bin:你好,你的demo项目是用eclipse开发的吗?我导入AS老是提示找不到SDK路径!不知道是什么问题
      • RetroX:这篇文章是我学MVVM以来感觉收益最大的文章,现在的项目就已经再用这种架构了,自己也在继续学习拓展这种mvvm的思维
      • 骑马倚斜桥w:有个问题 如果是view层想要动态添加view,而且不是recyclerview能搞定的 是一些特殊的view。根据网络数据回来的不同决定添不添加,这种情况如何能保证view层的纯净,不参加业务逻辑的处理呢,感觉viewmodel去做动态添加很别扭啊
        Kelin:@天青之枪 fragment 的替换属于view层的事,View层提供接口,viewmodel 层把数据处理好当成参数 调用View层接口 就可以了
        骑马倚斜桥w:@Kelin 对 而且还有添加fragment的问题 也是根据数据的不同 同一个activity可能会添加不同的fragment,这种感觉只能在view层根据数据取load不同的fragment
        Kelin:@天青之枪 view层提供个接口,把数据处理好再调View层方法,或者BindingAdapter 自己加个绑定属性,把数据帮进去。有些老控件不好处理 也是没办法的
      • 努力2009:真厉害,框架厉害,讲解也好,逻辑非常清晰,方便学习上手
      • 46648d8cd9b9:很赞的文章!WP转Android的想必看的泪流满面...

        3个问题

        1. 请问这个库是基于官方data binding 库么
        2. 数据的绑定是双向么
        3. 应用这个库,以及附加的库(rxjava, ),总共多大
        Kelin:@readercn 是基于官方data binding的,data binding 1.0已经是支持双向绑定的,大小没去注意,不过不会太大,rxjava和data binding 都不大,图片库默认用fresco 可能比较大了点,这个替换成自己的图片库(picasso 等)就行了
      • 679a3d031c4d:很好思路的一个项目,学习中,同时有一个问题想问问楼主,bindingadapter如果在library项目中是不是就不生效了呢!?
        Kelin:@Tp_yang bindingadapter如果在library项目中是不是就不生效了呢!不太懂你想表达的什么意思,bindingadapter 在哪都可以写,如果参数一样,会覆盖library中的 :smile:
      • 逍遥游lx:楼主还在吗?想请教下关于MVVM的问题,怎么找你啊?谢谢!
        Kelin:@luoxiong 发简信我 :smile:
      • 多喝岩浆y:楼主,麻烦问下为什么我导入你的项目会出现这个问题Error:A problem occurred configuring project ':app'.
        > Could not find adapters.jar (com.android.databinding:adapters:1.1).
        Searched in the following locations:
        https://jcenter.bintray.com/com/android/databinding/adapters/1.1/adapters-1.1.jar
        说找不到这个library呢
        _浮生若梦:@JWWWZ gradle版本改成2.1.1或者2.1.0都行,2.2.1会报这个错
      • 码海浮沉:Error:No service of type Factory<LoggingManagerInternal> available in ProjectScopeServices.提示这个怎么解决?
        没有名字的喵:http://blog.csdn.net/u014731991/article/details/52596981
        Kelin:@码海浮沉 这个看能解决你的问题吗?http://stackoverflow.com/questions/38825451/no-service-of-type-factory-available-in-projectscopeservices :smile:
        逍遥游lx:@码海浮沉 同样的错误,兄弟你怎么解决了啊?
      • Mr_468:马克
      • 829446681cbd:博主 我最近也在用mvvm看了你的文章 获益匪浅 我想问一下对于 上拉加载更多 定制上拉加载中的view和没有更多内容的view这种场景 如何用data binding 来简洁实现
        Kelin:@829446681cbd 单纯使用XML布局绑定的话,可能不好实现,可以用一个ObservableFiled设置监听,在ObservableFiled发生变化时,在View层(Activity )中用java代码去控制上拉加载中的view 的展示和动画等。 :smile:
      • d13457a15d53:你好,我发现一个问题:
        main_layout.xml :
        <include
        layout:some.xml
        bind:someViewModel />

        some.xml :
        <RecyclerView
        bind:items="someViewModel.items"
        bind:itemView="someViewModel.itemView"/>

        这样嵌套一层 就会报空指针异常
        E/AndroidRuntime: FATAL EXCEPTION: main
        java.lang.NullPointerException
        at me.tatarka.bindingcollectionadapter.ItemViewArg.select(ItemViewArg.java:29)
        at me.tatarka.bindingcollectionadapter.BindingRecyclerViewAdapter.getItemViewType(BindingRecyclerViewAdapter.java:157)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4960)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4913)

        但是只用一层的话 就不会报这个问题
        我检查过很多次 代码应该没问题
        只要嵌套一层 include就会报错
        请问您知道这问题吗?
      • simonjian:请问作者,你在项目中有用到这个库吗
        Kelin:@simonjian 恩恩 目前项目中正在使用,用起来还是比较方便,基本没有什么重大Bug或者内存泄露,可以把库clone下来,自己去做一些想要的定制。 :smile:
      • b6c7a3ded5ff:最近新的项目准备开始了,准备用一下mvvm的思想,后面多交流交流啊
        Kelin:@dyhuang 好的,我那个工具库,可以试着用一下,我们自己的项目也在用,有些东西方便很多 :smile:
      • risemoon:由浅到深的讲解,赞,对于想接触mvvm的人有很大帮助
      • 924e23426c1e:楼主,我一直有一个疑问啊,为什么大家都在View Model里保存context而不是activity呢,比如我要做界面跳转,不是activity更方便一点吗?
        924e23426c1e:@金玉有心 或者是使用base activity里面的一些东西
      • 花老鼠:学这些模式有用吗?? 之前用mvp写过某个功能,感觉维护起来很麻烦,总是刻意的在写,后来看到一个人说,写代码是为了理解起来简单,维护起来方便,不用刻意的追求什么模式。
        ITIan:@花老鼠 所以说国内有大部分人都在抄,没有自己点想法,整个安卓环境差的.....跟在外企工作的老鸟聊过,国内很多外包公司出的代码人家压根看不上眼,代码不严谨,质量太差,逻辑混杂,一个需求更改要很长时间.一个好的模式框架,是可以让你很从容地应对各种需求
        天之界线2010:@Kelin 代码改的多了,自然就明白了。这个其实不用多说的
        Kelin:@花老鼠 模式肯定不是特意去写的,只是用着用着总结出来的设计,使各个模块解藕,代码分层清晰,结构明朗,后人和团队都好维护和扩展,如果只是一味的实现功能不稍做设计,多次迭代之后,会越来越乱,总而言之,写些个模式是业界多年来总结出来和实践出来,慢慢扩展到各个平台,存在就必定有它的价值所在。
      • 8130566994ef:现在Android Data Binding已经实现了你说的ImageView加载url的问题

        @BindingAdapter({"bind:imageUrl", "bind:error"})
        public static void loadImage(ImageView view, String url, Drawable error) {
        Picasso.with(view.getContext()).load(url).error(error).into(view);
        }

        <ImageView app:imageUrl="@{venue.imageUrl}"
        app:error="@{@drawable/venueError}"/>

        :sunglasses:
        8130566994ef:@Kelin 这么说也对
        Kelin:@哼哼de那首歌 Android Data Binding 并没有帮你做这些事,还是需要自己去实现的 :smile:
      • Jason_cyj:原来只是知道有MVVM 从未深入了解 看了大神文章受益匪浅 但也有很多不是很理解 先下了代码 到时候有问题再请教
      • df4331726f9c:花了3天的时间看了这个项目,写的很棒 :+1:
        Kelin:@df4331726f9c 看看代码,可能对本文的理解更深刻些 :smile:
      • HannyYeung:哥們,看你寫東西都是在用心寫啊,
      • 429a3c98fb93:不错,算是吃螃蟹的了,但是新项目还是不敢尝试,怕遇到坑浪费开发时间
      • louiszgm:很好! 终于看到有捣鼓MVVM的了~~~我也在项目中开始了MVVM~~有机会交流一下哈 ~有好多问题想请教楼主:smile:
        Kelin:@louiszgm 好的 :smile:
      • 天之界线2010:不知道博主有看过这个库没,可以评价一下。https://github.com/tianzhijiexian/DBinding
        天之界线2010:@Kelin 好的 好的 期待交流
        Kelin:@天之界线2010 :blush:看到github你给我的issue了 也看了你的项目DBinding 了自动化插件等都很赞 感觉有很多借鉴的地方 文档写得很用心 文章比较长,最近比较忙,我大致过了一下,可能想法中间有些不太一致,等回头时间充裕下我再仔细看看,大家一起探讨探讨:smile:
      • LeoYe168:databind有个问题,根据数据判断某个空间visible或gone时,databind方式默认是visible。 因此在界面上有很明显的闪的效果。。题主有好的解决办法?google没啥好办法,有人说是系统一个bug
        7650769b4b02:@LeoYe168 binding.executePendingBindings()
        LeoYe168:@Kelin 项目中是从接口获得的数据,而且是在列表中。。 界面默认是visible,数据来了判断为gone,所以滑动页面闪的效果就很明显了。
        Kelin:@LeoYe168 我特意试了一下VISIBLE和GONE的问题,但是没有发现很明显的闪的问题,可能是我没完全懂你描述的问题?
      • 0f7d096211a6:command绑定到xml上,这样好吗,感觉有耦合
        Kelin:@hfox09211 Command 只是对事件的封装,更方便处理想要的事件,不关心UI相关事情,在处理控件只处理相关的数据就可以了,绑定到xml意思就是绑定到控件上,也就是给控件添加事件处理监听而已,和耦合应该是关系不大。 :smiley:

      本文标题:如何构建Android MVVM应用程序

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