框架模式 MVC 在Android中的使用

在学习Android开发2年的历史中,基本掌握了Android的基础知识。越到后面的学习越感觉困难,一来是自认为android没啥可学的了(自认为的,其实还有很多知识科学),二来网络上的很多框架已经帮我们做了太多的事情了,我们只需要画画UI就可以了,感觉Android开发没有太多的技术含金量。*近闲来无事,开始总结之前学过的知识点,想着是否应该学点其他的东西呢?总不能局限于Android基础知识吧。慢慢的探索发现在大的项目工程中,一个好的框架,好的设计模式,能减少很大的工作量。因此接下来两篇博客来学习一下Android中常用的两种框架设计模式 MVC和MVP。

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层。说了这么多,听着感觉很抽象,废话不多说,我们来看看MVC在Android开发中是怎么应用的吧!

 

MVC for Android

在Android开发中,比较流行的开发框架模式采用的是MVC框架模式,采用MVC模式的好处是便于UI界面部分的显示和业务逻辑,数据处理分开。那么Android项目中哪些代码来充当M,V,C角色呢?

M层:适合做一些业务逻辑处理,比如数据库存取操作,网络操作,复杂的算法,耗时的任务等都在model层处理。 V层:应用层中处理数据显示的部分,XML布局可以视为V层,显示Model层的数据结果。 C层:在Android中,Activity处理用户交互问题,因此可以认为Activity是控制器,Activity读取V视图层的数据(eg.读取当前EditText控件的数据),控制用户输入(eg.EditText控件数据的输入),并向Model发送数据请求(eg.发起网络请求等)。

接下来我们通过一个获取天气预报数据的小项目来解读 MVC for Android。先上一个界面图:

加载中...

Controller控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package com.***.androidmvcdemo.controller;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.***.androidmvcdemo.R;
import com.***.androidmvcdemo.entity.Weather;
import com.***.androidmvcdemo.entity.WeatherInfo;
import com.***.androidmvcdemo.model.OnWeatherListener;
import com.***.androidmvcdemo.model.WeatherModel;
import com.***.androidmvcdemo.model.WeatherModelImpl;
public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {
    private WeatherModel weatherModel;
    private Dialog loadingDialog;
    private EditText cityNOInput;
    private TextView city;
    private TextView cityNO;
    private TextView temp;
    private TextView wd;
    private TextView ws;
    private TextView sd;
    private TextView wse;
    private TextView time;
    private TextView njd;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        weatherModel = new WeatherModelImpl();
        initView();
    }
    /**
     * 初始化View
     */
    private void initView() {
        cityNOInput = findView(R.id.et_city_no);
        city = findView(R.id.tv_city);
        cityNO = findView(R.id.tv_city_no);
        temp = findView(R.id.tv_temp);
        wd = findView(R.id.tv_WD);
        ws = findView(R.id.tv_WS);
        sd = findView(R.id.tv_SD);
        wse = findView(R.id.tv_WSE);
        time = findView(R.id.tv_time);
        njd = findView(R.id.tv_njd);
        findView(R.id.btn_go).setOnClickListener(this);
        loadingDialog = new ProgressDialog(this);
        loadingDialog.setTitle(加载天气中...);
    }
    /**
     * 显示结果
     *
     * @param weather
     */
    public void displayResult(Weather weather) {
        WeatherInfo weatherInfo = weather.getWeatherinfo();
        city.setText(weatherInfo.getCity());
        cityNO.setText(weatherInfo.getCityid());
        temp.setText(weatherInfo.getTemp());
        wd.setText(weatherInfo.getWD());
        ws.setText(weatherInfo.getWS());
        sd.setText(weatherInfo.getSD());
        wse.setText(weatherInfo.getWSE());
        time.setText(weatherInfo.getTime());
        njd.setText(weatherInfo.getNjd());
    }
    /**
     * 隐藏进度对话框
     */
    public void hideLoadingDialog() {
        loadingDialog.dismiss();
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_go:
                loadingDialog.show();
                weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
                break;
        }
    }
    @Override
    public void onSuccess(Weather weather) {
        hideLoadingDialog();
        displayResult(weather);
    }
    @Override
    public void onError() {
        hideLoadingDialog();
        Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show();
    }
    private  T findView(int id) {
        return (T) findViewById(id);
    }
}

从上面代码可以看到,Activity持有了WeatherModel模型的对象,当用户有点击Button交互的时候,Activity作为Controller控制层读取View视图层EditTextView的数据,然后向Model模型发起数据请求,也就是调用WeatherModel对象的方法 getWeathre()方法。当Model模型处理数据结束后,通过接口OnWeatherListener通知View视图层数据处理完毕,View视图层该更新界面UI了。然后View视图层调用displayResult()方法更新UI。至此,整个MVC框架流程就在Activity中体现出来了。

Model模型

来看看WeatherModelImpl代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.***.androidmvcdemo.model;
/**
 * Description:请求网络数据接口
 * User: ***
 * Date: 2015/6/3
 * Time: 15:40
 */
public interface WeatherModel {
    void getWeather(String cityNumber, OnWeatherListener listener);
}
................
package com.***.androidmvcdemo.model;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.***.androidmvcdemo.entity.Weather;
import com.***.androidmvcdemo.volley.VolleyRequest;
/**
 * Description:从网络获取天气信息接口实现
 * User: ***
 * Date: 2015/6/3
 * Time: 15:40
 */
public class WeatherModelImpl implements WeatherModel {
    @Override
    public void getWeather(String cityNumber, final OnWeatherListener listener) {
        /*数据层操作*/
        VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html,
                Weather.class, new Response.Listener() {
                    @Override
                    public void onResponse(Weather weather) {
                        if (weather != null) {
                            listener.onSuccess(weather);
                        } else {
                            listener.onError();
                        }
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        listener.onError();
                    }
                });
    }
}

以上代码看出,这里设计了一个WeatherModel模型接口,然后实现了接口WeatherModelImpl类。controller控制器activity调用WeatherModelImpl类中的方法发起网络请求,然后通过实现OnWeatherListener接口来获得网络请求的结果通知View视图层更新UI 。至此,Activity就将View视图显示和Model模型数据处理隔离开了。activity担当contronller完成了model和view之间的协调作用。

至于这里为什么不直接设计成类里面的一个getWeather()方法直接请求网络数据?你考虑下这种情况:现在代码中的网络请求是使用Volley框架来实现的,如果哪天老板非要你使用Afinal框架实现网络请求,你怎么解决问题?难道是修改 getWeather()方法的实现? no no no,这样修改不仅破坏了以前的代码,而且还不利于维护, 考虑到以后代码的扩展和维护性,我们选择设计接口的方式来解决着一个问题,我们实现另外一个WeatherModelWithAfinalImpl类,继承自WeatherModel,重写里面的方法,这样不仅保留了以前的WeatherModelImpl类请求网络方式,还增加了WeatherModelWithAfinalImpl类的请求方式。Activity调用代码无需要任何修改。

MVC使用总结

利用MVC设计模式,使得这个天气预报小项目有了很好的可扩展和维护性,当需要改变UI显示的时候,无需修改Contronller(控制器)Activity的代码和Model(模型)WeatherModel模型中的业务逻辑代码,很好的将业务逻辑和界面显示分离。

在Android项目中,业务逻辑,数据处理等担任了Model(模型)角色,XML界面显示等担任了View(视图)角色,Activity担任了Contronller(控制器)角色。contronller(控制器)是一个中间桥梁的作用,通过接口通信来协同 View(视图)和Model(模型)工作,起到了两者之间的通信作用。

什么时候适合使用MVC设计模式?当然一个小的项目且无需频繁修改需求就不用MVC框架来设计了,那样反而觉得代码过度设计,代码臃肿。一般在大的项目中,且业务逻辑处理复杂,页面显示比较多,需要模块化设计的项目使用MVC就有足够的优势了。

4.在MVC模式中我们发现,其实控制器Activity主要是起到解耦作用,将View视图和Model模型分离,虽然Activity起到交互作用,但是找Activity中有很多关于视图UI的显示代码,因此View视图和Activity控制器并不是完全分离的,也就是说一部分View视图和Contronller控制器Activity是绑定在一个类中的。

MVC的优点:

(1)耦合性低。所谓耦合性就是模块代码之间的关联程度。利用MVC框架使得View(视图)层和Model(模型)层可以很好的分离,这样就达到了解耦的目的,所以耦合性低,减少模块代码之间的相互影响。

(2)可扩展性好。由于耦合性低,添加需求,扩展代码就可以减少修改之前的代码,降低bug的出现率。

(3)模块职责划分明确。主要划分层M,V,C三个模块,利于代码的维护。

android中MVC,MVP和MVVM三种模式详解析

我们都知道,Android本身就采用了MVC模式,model层数据源层我们就不说了,至于view层即通过xml来体现,而 controller层的角色一般是由activity来担当的。虽然我们项目用到了MVP模式,但是现在人们并没有总结出一种规范,所以MVP模式的写法并不统一,而至于MVVM模式看网上的呼声似乎也是赞同和拍砖的参半,所以对于这几种模式我也不发表意见了,适合自己的才是*好的。下面是我看到的关于这几种模式的几篇文章,整合了一下分享给大家。

 

相信大家对MVC,MVP和MVVM都不陌生,作为三个*耳熟能详的Android框架,它们的应用可以是非常广泛的,但是对于一些新手来说,可能对于区分它们三个都有困难,更别说在实际的项目中应用了,有些时候想用MVP的,代码写着写着就变成了MVC,久而久之就对它们三个的选择产生了恐惧感,如果你也是这样的人群,那么这篇文章可能会对你有很大的帮助,希望大家看完都会有收获吧!

文章重点:

(1)了解并区分MVC,MVP,MVVM。

(2)知道这三种模式在Android中如何使用。

(3)走出data binding的误区。

(4)了解MVP+data binding的开发模式。

本篇文章的demo我将会上传到我的github上。

水之积也不厚,则其负大舟也无力

正如庄子在逍遥游中说的,如果水不够深,那就没有能够担负大船的力量 。所以在真正开始涉及具体的代码之前,我们要先对MVC,MVP和MVVM做一个初步的了解。如果各位同学对此已经有所了解了,可以选择性跳过这一节。

MVC

MVC,Model View Controller,是软件架构中*常见的一种框架,简单来说就是通过controller的控制去操作model层的数据,并且返回给view层展示,具体见下图

%title插图%num

当用户出发事件的时候,view层会发送指令到controller层,接着controller去通知model层更新数据,model层更新完数据以后直接显示在view层上,这就是MVC的工作原理。

那具体到Android上是怎么样一个情况呢?

大家都知道一个Android工程有什么对吧,有Java的class文件,有res文件夹,里面是各种资源,还有类似manifest文件等等。对于原生的Android项目来说,layout.xml里面的xml文件就对应于MVC的view层,里面都是一些view的布局代码,而各种java bean,还有一些类似repository类就对应于model层,至于controller层嘛,当然就是各种activity咯。大家可以试着套用我上面说的MVC的工作原理是理解。比如你的界面有一个按钮,按下这个按钮去网络上下载一个文件,这个按钮是view层的,是使用xml来写的,而那些和网络连接相关的代码写在其他类里,比如你可以写一个专门的networkHelper类,这个就是model层,那怎么连接这两层呢?是通过button.setOnClickListener()这个函数,这个函数就写在了activity中,对应于controller层。是不是很清晰。

大家想过这样会有什么问题吗?显然是有的,不然为什么会有MVP和MVVM的诞生呢,是吧。问题就在于xml作为view层,控制能力实在太弱了,你想去动态的改变一个页面的背景,或者动态的隐藏/显示一个按钮,这些都没办法在xml中做,只能把代码写在activity中,造成了activity既是controller层,又是view层的这样一个窘境。大家回想一下自己写的代码,如果是一个逻辑很复杂的页面,activity或者fragment是不是动辄上千行呢?这样不仅写起来麻烦,维护起来更是噩梦。(当然看过Android源码的同学其实会发现上千行的代码不算啥,一个RecyclerView.class的代码都快上万行了呢。。)

MVC还有一个重要的缺陷,大家看上面那幅图,view层和model层是相互可知的,这意味着两层之间存在耦合,耦合对于一个大型程序来说是非常致命的,因为这表示开发,测试,维护都需要花大量的精力。

正因为MVC有这样那样的缺点,所以才演化出了MVP和MVVM这两种框架。

MVP

MVP作为MVC的演化,解决了MVC不少的缺点,对于Android来说,MVP的model层相对于MVC是一样的,而activity和fragment不再是controller层,而是纯粹的view层,所有关于用户事件的转发全部交由presenter层处理。下面还是让我们看图

%title插图%num

从图中就可以看出,*明显的差别就是view层和model层不再相互可知,完全的解耦,取而代之的presenter层充当了桥梁的作用,用于操作view层发出的事件传递到presenter层中,presenter层去操作model层,并且将数据返回给view层,整个过程中view层和model层完全没有联系。看到这里大家可能会问,虽然view层和model层解耦了,但是view层和presenter层不是耦合在一起了吗?其实不是的,对于view层和presenter层的通信,我们是可以通过接口实现的,具体的意思就是说我们的activity,fragment可以去实现实现定义好的接口,而在对应的presenter中通过接口调用方法。不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Presenter的测试。这就解决了MVC模式中测试,维护难的问题。

当然,其实*好的方式是使用fragment作为view层,而activity则是用于创建view层(fragment)和presenter层(presenter)的一个控制器。

MVVM

MVVM*早是由微软提出的

%title插图%num

这里要感谢泡在网上的日子,因为前面看到的三张图我都是从它的博客中摘取的,如果有人知道不允许这样做的话请告诉我,我会从我的博客中删除的,谢谢。

从图中看出,它和MVP的区别貌似不大,只不过是presenter层换成了viewmodel层,还有一点就是view层和viewmodel层是相互绑定的关系,这意味着当你更新viewmodel层的数据的时候,view层会相应的变动ui。

我们很难去说MVP和MVVM这两个MVC的变种孰优孰劣,还是要具体情况具体分析。

纸上得来终觉浅,*知此事要躬行

对于程序员来说,空谈是*没效率的一种方式,相信大家看了我上面对于三种模式的分析,或多或少都会有点云里雾里,下面让我们结合代码来看看。

让我们试想一下下面这个情景,用户点击一个按钮A,获取github上对应公司对应仓库中贡献排行*的任的名字,然后我们还会有一个按钮B,用户点击按钮B,界面上排行*的那个人的名字就会换成自己的。

MVC

MVC实现是*简单的。

首先看对应view层的xml文件

[html]
  1. <?xmlversionxmlversion=”1.0″encoding=”utf-8″?>  
  2. <LinearLayoutxmlns:androidLinearLayoutxmlns:android=“http://schemas.android.com/apk/res/android”  
  3.     xmlns:tools=“http://schemas.android.com/tools”  
  4.     android:layout_width=“match_parent”  
  5.     android:layout_height=“match_parent”  
  6.     android:id=“@+id/container”  
  7.     android:orientation=“vertical”  
  8.     tools:context=“.ui.view.MainActivity”  
  9.     android:fitsSystemWindows=“true”>  
  10.     <Button  
  11.         android:text=“get”  
  12.         android:layout_width=“match_parent”  
  13.         android:layout_height=“wrap_content”  
  14.         android:onClick=“get”/>  
  15.     <Button  
  16.         android:text=“change”  
  17.         android:layout_width=“match_parent”  
  18.         android:layout_height=“wrap_content”  
  19.         android:onClick=“change”/>  
  20.     <TextView  
  21.         android:id=“@+id/top_contributor”  
  22.         android:layout_width=“match_parent”  
  23.         android:layout_height=“match_parent”  
  24.         android:gravity=“center”  
  25.         android:textSize=“30sp”/>  
  26. </LinearLayout>  

很简单,两个Button一个TextView

接着看对应controller层的activity

[java]
  1. public class MainActivity extends AppCompatActivity {  
  2.     private ProcessDialog dialog;  
  3.     private Contributor contributor = new Contributor();  
  4.     private TextView topContributor;  
  5.     private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  6.         @Override  
  7.         public void onStart() {  
  8.             showProgress();
  9.         }
  10.         @Override  
  11.         public void onCompleted() {  
  12.         }
  13.         @Override  
  14.         public void onError(Throwable e) {  
  15.         }
  16.         @Override  
  17.         public void onNext(Contributor contributor) {  
  18.             MainActivity.this.contributor = contributor;  
  19.             topContributor.setText(contributor.login);
  20.             dismissProgress();
  21.         }
  22.     };
  23.     @Override  
  24.     protected void onCreate(Bundle savedInstanceState) {  
  25.         super.onCreate(savedInstanceState);  
  26.         setContentView(R.layout.activity_main);
  27.         topContributor = (TextView)findViewById(R.id.top_contributor);
  28.     }
  29.     public void get(View view){  
  30.         getTopContributor(“square”, “retrofit”);  
  31.     }
  32.     public void change(View view){  
  33.         contributor.login = “zjutkz”;  
  34.         topContributor.setText(contributor.login);
  35.     }
  36.     public void getTopContributor(String owner,String repo){  
  37.         GitHubApi.getContributors(owner, repo)
  38.                 .take(1)  
  39.                 .observeOn(AndroidSchedulers.mainThread())
  40.                 .subscribeOn(Schedulers.newThread())
  41.                 .map(new Func1<List<Contributor>, Contributor>() {  
  42.                     @Override  
  43.                     public Contributor call(List<Contributor> contributors) {  
  44.                         return contributors.get(0);  
  45.                     }
  46.                 })
  47.                 .subscribe(contributorSub);
  48.     }
  49.     public void showProgress(){  
  50.         if(dialog == null){  
  51.             dialog = new ProcessDialog(this);  
  52.         }
  53.         dialog.showMessage(“正在加载…”);  
  54.     }
  55.    public void dismissProgress(){  
  56.         if(dialog == null){  
  57.             dialog = new ProcessDialog(this);  
  58.         }
  59.         dialog.dismiss();
  60.     }
  61. }

我们看一下get()方法中调用的getTopContributor方法

[java]
  1. public void getTopContributor(String owner,String repo){  
  2.     GitHubApi.getContributors(owner, repo)
  3.             .take(1)  
  4.             .observeOn(AndroidSchedulers.mainThread())
  5.             .subscribeOn(Schedulers.newThread())
  6.             .map(new Func1<List<Contributor>, Contributor>() {  
  7.                 @Override  
  8.                 public Contributor call(List<Contributor> contributors) {  
  9.                     return contributors.get(0);  
  10.                 }
  11.             })
  12.             .subscribe(contributorSub);
  13. }

 

熟悉rxjava和retrofit的同学应该都明白这是啥意思,如果对这两个开源库不熟悉也没事,可以参考给 Android 开发者的 RxJava 详解和用 Retrofit 2 简化 HTTP 请求这两篇文章。

对于这里大家只要知道这段代码的意思就是去获取github上owner公司中的repo仓库里贡献排名*的那个人。贡献者是通过Contributor这个java bean存储的。

[java]
  1. public class Contributor {  
  2.     public String login;  
  3.     public int contributions;  
  4.   @Override  
  5.     public String toString() {  
  6.         return login + “, ” + contributions;  
  7.     }
  8. }

 

很简单,login表示贡献者的名字,contributor表示贡献的次数。

然后通过rxjava的subscriber中的onNext()函数得到这个数据。

[java]
  1. private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  2.     @Override  
  3.     public void onStart() {  
  4.         showProgress();
  5.     }
  6.     @Override  
  7.     public void onCompleted() {  
  8.     }
  9.     @Override  
  10.     public void onError(Throwable e) {  
  11.     }
  12.     @Override  
  13.     public void onNext(Contributor contributor) {  
  14.         MainActivity.this.contributor = contributor;  
  15.         topContributor.setText(contributor.login);
  16.         dismissProgress();
  17.     }
  18. };

至于另外那个change按钮的工作大家应该都看得懂,这里不重复了。

好了,我们来回顾一遍整个流程。

首先在xml中写好布局代码。

其次,activity作为一个controller,里面的逻辑是监听用户点击按钮并作出相应的操作。比如针对get按钮,做的工作就是调用GithubApi的方法去获取数据。

GithubApi,Contributor等类则表示MVC中的model层,里面是数据和一些具体的逻辑操作。

说完了流程再来看看问题,还记得我们前面说的吗,MVC在Android上的应用,一个具体的问题就是activity的责任过重,既是controller又是view。这里是怎么体现的呢?看了代码大家发现其中有一个progressDialog,在加载数据的时候显示,加载完了以后取消,逻辑其实是view层的逻辑,但是这个我们没办法写到xml里面啊,包括TextView.setTextView(),这个也一样。我们只能把这些逻辑写到activity中,这就造成了activity的臃肿,这个例子可能还好,如果是一个复杂的页面呢?大家自己想象一下。

MVP

通过具体的代码大家知道了MVC在Android上是如何工作的,也知道了它的缺点,那MVP是如何修正的呢?

这里先向大家推荐github上的一个第三方库,通过这个库大家可以很轻松的实现MVP。好了,还是看代码吧。

首先还是xml

[html]
  1. <?xml version=“1.0” encoding=”utf-8″?>  
  2. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”  
  3.     xmlns:tools=“http://schemas.android.com/tools”  
  4.     android:layout_width=“match_parent”  
  5.     android:layout_height=“match_parent”  
  6.     android:id=“@+id/container”  
  7.     android:orientation=“vertical”  
  8.     tools:context=“.ui.view.MainActivity”  
  9.     android:fitsSystemWindows=“true”>  
  10.     <Button  
  11.         android:text=“get”  
  12.         android:layout_width=“match_parent”  
  13.         android:layout_height=“wrap_content”  
  14.         android:onClick=“get”/>  
  15.     <Button  
  16.         android:text=“change”  
  17.         android:layout_width=“match_parent”  
  18.         android:layout_height=“wrap_content”  
  19.         android:onClick=“change”/>  
  20.     <TextView  
  21.         android:id=“@+id/top_contributor”  
  22.         android:layout_width=“match_parent”  
  23.         android:layout_height=“match_parent”  
  24.         android:gravity=“center”  
  25.         android:textSize=“30sp”/>  
  26. </LinearLayout>  

 

这个和MVC是一样的,毕竟界面的形式是一样的嘛。

接下去,我们看一个接口。

[java]
  1. public interface ContributorView extends MvpView {  
  2.     void onLoadContributorStart();  
  3.     void onLoadContributorComplete(Contributor topContributor);  
  4.     void onChangeContributorName(String name);  
  5. }

这个接口起什么作用呢?还记得我之前说的吗?MVP模式中,view层和presenter层靠的就是接口进行连接,而具体的就是上面的这个了,里面定义的三个方法,*个是开始获取数据,第二个是获取数据成功,第三个是改名。我们的view层(activity)只要实现这个接口就可以了。

下面看activity的代码

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {  
  2.     private ProcessDialog dialog;  
  3.     private TextView topContributor;  
  4.     @Override  
  5.     protected void onCreate(Bundle savedInstanceState) {  
  6.         super.onCreate(savedInstanceState);  
  7.         setContentView(R.layout.activity_main);
  8.         topContributor = (TextView)findViewById(R.id.top_contributor);
  9.     }
  10.     @NonNull  
  11.     @Override  
  12.     public ContributorPresenter createPresenter() {  
  13.         return new ContributorPresenter();  
  14.     }
  15.     public void get(View view){  
  16.         getPresenter().get(“square”, “retrofit”);  
  17.     }
  18.     public void change(View view){  
  19.         getPresenter().change();
  20.     }
  21.     @Override  
  22.     public void onLoadContributorStart() {  
  23.         showProgress();
  24.     }
  25.     @Override  
  26.     public void onLoadContributorComplete(Contributor contributor) {  
  27.         topContributor.setText(contributor.toString());
  28.         dismissProgress();
  29.     }
  30.     @Override  
  31.     public void onChangeContributorName(String name) {  
  32.         topContributor.setText(name);
  33.     }
  34.     public void showProgress(){  
  35.         if(dialog == null){  
  36.             dialog = new ProcessDialog(this);  
  37.         }
  38.         dialog.showMessage(“正在加载…”);  
  39.     }
  40.     public void dismissProgress(){  
  41.         if(dialog == null){  
  42.             dialog = new ProcessDialog(this);  
  43.         }
  44.         dialog.dismiss();
  45.     }
  46. }

 

它继承自MvpActivity,实现了刚才的ContributorView接口。继承的那个MvpActivity大家这里不用太关心主要是做了一些初始化和生命周期的封装。我们只要关心这个activity作为view层,到底是怎么工作的。

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. public void get(View view){  
  2.     getPresenter().get(“square”, “retrofit”);  
  3. }
  4. public void change(View view){  
  5.     getPresenter().change();
  6. }

get()和change()这两个方法是我们点击按钮以后执行的,可以看到,里面完完全全没有任何和model层逻辑相关的东西,只是简单的委托给了presenter,那我们再看看presenter层做了什么

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. public class ContributorPresenter extends MvpBasePresenter<ContributorView> {  
  2.     private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  3.         @Override  
  4.         public void onStart() {  
  5.             ContributorView view = getView();
  6.             if(view != null){  
  7.                 view.onLoadContributorStart();
  8.             }
  9.         }
  10.         @Override  
  11.         public void onCompleted() {  
  12.         }
  13.         @Override  
  14.         public void onError(Throwable e) {  
  15.         }
  16.         @Override  
  17.         public void onNext(Contributor topContributor) {  
  18.             ContributorView view = getView();
  19.             if(view != null){  
  20.                 view.onLoadContributorComplete(topContributor);
  21.             }
  22.         }
  23.     };
  24.     public void get(String owner,String repo){  
  25.         GitHubApi.getContributors(owner, repo)
  26.                 .take(1)  
  27.                 .observeOn(AndroidSchedulers.mainThread())
  28.                 .subscribeOn(Schedulers.newThread())
  29.                 .map(new Func1<List<Contributor>, Contributor>() {  
  30.                     @Override  
  31.                     public Contributor call(List<Contributor> contributors) {  
  32.                         return contributors.get(0);  
  33.                     }
  34.                 })
  35.                 .subscribe(contributorSub);
  36.     }
  37.     public void change(){  
  38.         ContributorView view = getView();
  39.         if(view != null){  
  40.             view.onChangeContributorName(“zjutkz”);  
  41.         }
  42.     }
  43. }

其实就是把刚才MVC中activity的那部分和model层相关的逻辑抽取了出来,并且在相应的时机调用ContributorView接口对应的方法,而我们的activity是实现了这个接口的,自然会走到对应的方法中。好了,我们来捋一捋。

首先,和MVC*大的不同,MVP把activity作为了view层,通过代码也可以看到,整个activity没有任何和model层相关的逻辑代码,取而代之的是把代码放到了presenter层中,presenter获取了model层的数据之后,通过接口的形式将view层需要的数据返回给它就OK了。

这样的好处是什么呢?首先,activity的代码逻辑减少了,其次,view层和model层完全解耦,具体来说,如果你需要测试一个http请求是否顺利,你不需要写一个activity,只需要写一个java类,实现对应的接口,presenter获取了数据自然会调用相应的方法,相应的,你也可以自己在presenter中mock数据,分发给view层,用来测试布局是否正确。

%title插图%num

它竟然说data binding的viewmodel层是binding类,其实不止是这篇文章,其他有一些开发者写的关于data binding的文章里都犯了一样的错误。大家如果也有这样的概念,请务必纠正过来!!说完了错误的概念,那data binding中真正的viewmodel是什么呢?我们还是以之前MVC,MVP的那个例子做引导。首先是view层,这没啥好说的,和MVP一样,只不过多了数据绑定。view层就是xml和activity。

[html]
  1. <layout xmlns:android=“http://schemas.android.com/apk/res/android”>  
  2.     <data>  
  3.         <variable name=“contributor” type=”zjutkz.com.mvvm.viewmodel.Contributor”/>  
  4.     </data>  
  5.     <LinearLayout  
  6.         android:layout_width=“match_parent”  
  7.         android:layout_height=“match_parent”  
  8.         android:id=“@+id/container”  
  9.         android:orientation=“vertical”  
  10.         android:fitsSystemWindows=“true”>  
  11.         <Button  
  12.             android:id=“@+id/get”  
  13.             android:text=“get”  
  14.             android:layout_width=“match_parent”  
  15.             android:layout_height=“wrap_content”  
  16.             android:onClick=“get”/>  
  17.         <Button  
  18.             android:id=“@+id/change”  
  19.             android:text=“change”  
  20.             android:layout_width=“match_parent”  
  21.             android:layout_height=“wrap_content”  
  22.             android:onClick=“change”/>  
  23.         <TextView  
  24.             android:id=“@+id/top_contributor”  
  25.             android:layout_width=“match_parent”  
  26.             android:layout_height=“match_parent”  
  27.             android:gravity=“center”  
  28.             android:textSize=“30sp”  
  29.             android:text=“@{contributor.login}”/>  
  30.     </LinearLayout>  
  31. </layout>  

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. public class MainActivity extends AppCompatActivity {  
  2.     private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  3.         @Override  
  4.         public void onStart() {  
  5.             showProgress();
  6.         }
  7.         @Override  
  8.         public void onCompleted() {  
  9.         }
  10.         @Override  
  11.         public void onError(Throwable e) {  
  12.         }
  13.         @Override  
  14.         public void onNext(Contributor contributor) {  
  15.             binding.setContributor(contributor);
  16.             dismissProgress();
  17.         }
  18.     };
  19.     private ProcessDialog dialog;  
  20.     private MvvmActivityMainBinding binding;  
  21.     @Override  
  22.     protected void onCreate(Bundle savedInstanceState) {  
  23.         super.onCreate(savedInstanceState);  
  24.         binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);  
  25.     }
  26.     public void get(View view){  
  27.         getContributors(“square”, “retrofit”);  
  28.     }
  29.     public void change(View view){  
  30.         if(binding.getContributor() != null){  
  31.             binding.getContributor().setLogin(“zjutkz”);  
  32.         }
  33.     }
  34.     public void showProgress(){  
  35.         if(dialog == null){  
  36.             dialog = new ProcessDialog(this);  
  37.         }
  38.         dialog.showMessage(“正在加载…”);  
  39.     }
  40.     public void dismissProgress(){  
  41.         if(dialog == null){  
  42.             dialog = new ProcessDialog(this);  
  43.         }
  44.         dialog.dismiss();
  45.     }
  46.     public void getContributors(String owner,String repo){  
  47.         GitHubApi.getContributors(owner, repo)
  48.                 .take(1)  
  49.                 .observeOn(AndroidSchedulers.mainThread())
  50.                 .subscribeOn(Schedulers.newThread())
  51.                 .map(new Func1<List<Contributor>, Contributor>() {  
  52.                     @Override  
  53.                     public Contributor call(List<Contributor> contributors) {  
  54.                         return contributors.get(0);  
  55.                     }
  56.                 })
  57.                 .subscribe(contributorSub);
  58.     }
  59. }

如果你对data binding框架是有了解的,上面的代码你能轻松的看懂。

那model层又是什么呢?当然就是那些和数据相关的类,GithubApi等等。

重点来了,viewmodel层呢?好吧,viewmodel层就是是Contributor类!大家不要惊讶,我慢慢的来说。

[java]
  1. public class Contributor extends BaseObservable{  
  2.     private String login;  
  3.     private int contributions;  
  4.     @Bindable  
  5.     public String getLogin(){  
  6.         return login;  
  7.     }
  8.     @Bindable  
  9.     public int getContributions(){  
  10.         return contributions;  
  11.     }
  12.     public void setLogin(String login){  
  13.         this.login = login;  
  14.         notifyPropertyChanged(BR.login);
  15.     }
  16.     public void setContributions(int contributions){  
  17.         this.contributions = contributions;  
  18.         notifyPropertyChanged(BR.contributions);
  19.     }
  20.     @Override  
  21.     public String toString() {  
  22.         return login + “, ” + contributions;  
  23.     }
  24. }

我们可以看到,Contributor和MVP相比,继承自了BaseObservable,有基础的同学都知道这是为了当Contributor内部的variable改变的时候ui可以同步的作出响应。

我为什么说Contributor是一个viewmodel呢。大家还记得viewmodel的概念吗?view和viewmodel相互绑定在一起,viewmodel的改变会同步到view层,从而view层作出响应。这不就是Contributor和xml中那些组件元素的关系吗?所以,大家不要被binding类迷惑了,data binding框架中的viewmodel是自己定义的那些看似是model类的东西!比如这里的Contributor!

话说到这里,那binding类又是什么呢?其实具体对应到之前MVVM的那张图就很好理解了,我们想一下,binding类的工作是什么?

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. binding = DataBindingUtil.setContentView(this,R.layout.mvvm_activity_main);  
  2. binding.setContributor(contributor);

首先,binding要通过DataBindingUtil.setContentView()方法将xml,也就是view层设定。

接着,通过setXXX()方法将viewmodel层注入进去。

由于这两个工作,view层(xml的各个组件)和viewmodel层(contributor)绑定在了一起。

好了,大家知道了吗,binding类,其实就是上图中view和viewmodel中间的那根线啊!!

 

MVVM

MVVM的问题呢,其实和MVC有一点像。data binding框架解决了数据绑定的问题,但是view层还是会过重,大家可以看我上面那个MVVM模式下的activity

[java]
  1. public class MainActivity extends AppCompatActivity {  
  2.     private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  3.         @Override  
  4.         public void onStart() {  
  5.             showProgress();
  6.         }
  7.         @Override  
  8.         public void onCompleted() {  
  9.         }
  10.         @Override  
  11.         public void onError(Throwable e) {  
  12.         }
  13.         @Override  
  14.         public void onNext(Contributor contributor) {  
  15.             binding.setContributor(contributor);
  16.             dismissProgress();
  17.         }
  18.     };
  19.     private ProcessDialog dialog;  
  20.     private MvvmActivityMainBinding binding;  
  21.     @Override  
  22.     protected void onCreate(Bundle savedInstanceState) {  
  23.         super.onCreate(savedInstanceState);  
  24.         binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);  
  25.     }
  26.     public void get(View view){  
  27.         getContributors(“square”, “retrofit”);  
  28.     }
  29.     public void change(View view){  
  30.         if(binding.getContributor() != null){  
  31.             binding.getContributor().setLogin(“zjutkz”);  
  32.         }
  33.     }
  34.     public void showProgress(){  
  35.         if(dialog == null){  
  36.             dialog = new ProcessDialog(this);  
  37.         }
  38.         dialog.showMessage(“正在加载…”);  
  39.     }
  40.     public void dismissProgress(){  
  41.         if(dialog == null){  
  42.             dialog = new ProcessDialog(this);  
  43.         }
  44.         dialog.dismiss();
  45.     }
  46.     public void getContributors(String owner,String repo){  
  47.         GitHubApi.getContributors(owner, repo)
  48.                 .take(1)  
  49.                 .observeOn(AndroidSchedulers.mainThread())
  50.                 .subscribeOn(Schedulers.newThread())
  51.                 .map(new Func1<List<Contributor>, Contributor>() {  
  52.                     @Override  
  53.                     public Contributor call(List<Contributor> contributors) {  
  54.                         return contributors.get(0);  
  55.                     }
  56.                 })
  57.                 .subscribe(contributorSub);
  58.     }
  59. }

大家有没有发现,activity在MVVM中应该是view层的,但是里面却和MVC一样写了对model的处理。有人会说你可以把对model的处理放到viewmodel层中,这样不是更符合MVVM的设计理念吗?这样确实可以,但是progressDialog的show和dismiss呢?你怎么在viewmodel层中控制?这是view层的东西啊,而且在xml中也没有,我相信会有解决的方案,但是我们有没有一种更加便捷的方式呢?

 

首先还是view层。

[html]
  1. <layout xmlns:android=“http://schemas.android.com/apk/res/android”>  
  2.     <data>  
  3.         <variable name=“contributor” type=”zjutkz.com.mvpdatabinding.viewmodel.Contributor”/>  
  4.     </data>  
  5.     <LinearLayout  
  6.         android:layout_width=“match_parent”  
  7.         android:layout_height=“match_parent”  
  8.         android:id=“@+id/container”  
  9.         android:orientation=“vertical”  
  10.         android:fitsSystemWindows=“true”>  
  11.         <Button  
  12.             android:id=“@+id/get”  
  13.             android:text=“get”  
  14.             android:layout_width=“match_parent”  
  15.             android:layout_height=“wrap_content”  
  16.             android:onClick=“get”/>  
  17.         <Button  
  18.             android:id=“@+id/change”  
  19.             android:text=“change”  
  20.             android:layout_width=“match_parent”  
  21.             android:layout_height=“wrap_content”  
  22.             android:onClick=“change”/>  
  23.         <TextView  
  24.             android:id=“@+id/top_contributor”  
  25.             android:layout_width=“match_parent”  
  26.             android:layout_height=“match_parent”  
  27.             android:gravity=“center”  
  28.             android:textSize=“30sp”  
  29.             android:text=“@{contributor.login}”/>  
  30.     </LinearLayout>  
  31. </layout>  
[java]
  1. public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {  
  2.     private ProcessDialog dialog;  
  3.     private ActivityMainBinding binding;  
  4.     @Override  
  5.     protected void onCreate(Bundle savedInstanceState) {  
  6.         super.onCreate(savedInstanceState);  
  7.         binding = DataBindingUtil.setContentView(this, R.layout.activity_main);  
  8.     }
  9.     @NonNull  
  10.     @Override  
  11.     public ContributorPresenter createPresenter() {  
  12.         return new ContributorPresenter();  
  13.     }
  14.     public void get(View view){  
  15.         getPresenter().get(“square”, “retrofit”);  
  16.     }
  17.     public void change(View view){  
  18.         if(binding.getContributor() != null){  
  19.             binding.getContributor().setLogin(“zjutkz”);  
  20.         }
  21.     }
  22.     @Override  
  23.     public void onLoadContributorStart() {  
  24.         showProgress();
  25.     }
  26.     @Override  
  27.     public void onLoadContributorComplete(Contributor contributor) {  
  28.         binding.setContributor(contributor);
  29.         dismissProgress();
  30.     }
  31.     public void showProgress(){  
  32.         if(dialog == null){  
  33.             dialog = new ProcessDialog(this);  
  34.         }
  35.         dialog.showMessage(“正在加载…”);  
  36.     }
  37.     public void dismissProgress(){  
  38.         if(dialog == null){  
  39.             dialog = new ProcessDialog(this);  
  40.         }
  41.         dialog.dismiss();
  42.     }
  43. }

然后是presenter层

[java]
  1. public class ContributorPresenter extends MvpBasePresenter<ContributorView> {  
  2.     private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  3.         @Override  
  4.         public void onStart() {  
  5.             ContributorView view = getView();
  6.             if(view != null){  
  7.                 view.onLoadContributorStart();
  8.             }
  9.         }
  10.         @Override  
  11.         public void onCompleted() {  
  12.         }
  13.         @Override  
  14.         public void onError(Throwable e) {  
  15.         }
  16.         @Override  
  17.         public void onNext(Contributor topContributor) {  
  18.             ContributorView view = getView();
  19.             if(view != null){  
  20.                 view.onLoadContributorComplete(topContributor);
  21.             }
  22.         }
  23.     };
  24.     public void get(String owner,String repo){  
  25.         GitHubApi.getContributors(owner, repo)
  26.                 .take(1)  
  27.                 .observeOn(AndroidSchedulers.mainThread())
  28.                 .subscribeOn(Schedulers.newThread())
  29.                 .map(new Func1<List<Contributor>, Contributor>() {  
  30.                     @Override  
  31.                     public Contributor call(List<Contributor> contributors) {  
  32.                         return contributors.get(0);  
  33.                     }
  34.                 })
  35.                 .subscribe(contributorSub);
  36.     }
  37. }

model层就是GithubApi等等。

我们使用了data binding框架去节省了类似findViewById和数据绑定的时间,又使用了presenter去将业务逻辑和view层分离。

当然这也不是固定的,你大可以在viewmodel中实现相应的接口,presenter层的数据直接发送到viewmodel中,在viewmodel里更新,因为view和viewmodel是绑定的,这样view也会相应的作出反应。

说到这里,我还是想重复刚才的那句话,*佳实践都是人想出来的,用这些框架根本的原因也是为了尽量低的耦合性和尽量高的可复用性。