标签: MVP

关于 MVP in Android

 

一、概述

对于MVP(Model View Presenter),大多数人都能说出一二:“MVC的演化版本”,“让Model和View完全解耦”等等。本篇博文仅是为了做下记录,提出一些自己的看法,和帮助大家如何针对一个Activity页面去编写针对MVP风格的代码。

对于MVP,我的内心有一个问题:

为何这个模式出来后,就能被广大的Android的程序员接受呢?

问了些程序员,他们对于MVP的普遍的认识是:“代码很清晰,不过增加了很多类”。我在*次看到MVP的时候,看了一个demo,看完以后觉得非常nice(但是回过头来,自己想个例子写,就头疼写不出来,当然这在后文会说)。nice的原因还是因为,这个模式的确让代码的清晰度有了很大的提升。

那么,提升一般都是对比出来的,回顾下,没有应用MVP的代码结构。很多人说明显是MVC么:

  • View:对应于布局文件
  • Model:业务逻辑和实体模型
  • Controllor:对应于Activity

看起来的确像那么回事,但是细细的想想这个View对应于布局文件,其实能做的事情特别少,实际上关于该布局文件中的数据绑定的操作,事件处理的代码都在Activity中,造成了Activity既像View又像Controller(当然了Data-Binder的出现,可能会让View更像View吧)。这可能也就是为何,在该文中有一句这样的话:

Most of the modern Android applications just use View-Model architecture,everything is connected with Activity.

而当将架构改为MVP以后,Presenter的出现,将Actvity视为View层,Presenter负责完成View层与Model层的交互。现在是这样的:

  • View 对应于Activity,负责View的绘制以及与用户交互
  • Model 依然是业务逻辑和实体模型
  • Presenter 负责完成View于Model间的交互

ok,先简单了解下,文中会有例子到时候可以直观的感受下。

小总结下,也就是说,之所以让人觉得耳目一新,是因为这次的跳跃是从并不标准的MVCMVP的一个转变,减少了Activity的职责,简化了Activity中的代码,将复杂的逻辑代码提取到了Presenter中进行处理。与之对应的好处就是,耦合度更低,更方便的进行测试。借用两张图(出自:该文),代表上述的转变:

%title插图%num

转变为:

%title插图%num

二、MVP 与 MVC 区别

ok,上面说了一堆理论,下面我们还是需要看一看MVC与MVP的一个区别,请看下图(来自:本文):

%title插图%num

其实*明显的区别就是,MVC中是允许Model和View进行交互的,而MVP中很明显,Model与View之间的交互由Presenter完成。还有一点就是Presenter与View之间的交互是通过接口的(代码中会体现)。

还有一堆概念性的东西,以及优点就略了,有兴趣自行百度。下面还是通过一些简单的需求来展示如何编写MVP的demo。

三、Simple Login Demo

效果图:

%title插图%num

看到这样的效果,先看下完工后的项目结构:

%title插图%num

ok,接下来开始一步一步的编写思路。

(一)Model

首先实体类User不用考虑这个肯定有,其次从效果图可以看到至少有一个业务方法login(),这两点没什么难度,我们首先完成:

package com.zhy.blogcodes.mvp.bean;

/**
 * Created by zhy on 15/6/18.
 */
public class User
{
    private String username ;
    private String password ;

    public String getUsername()
    {
        return username;
    }

    public void setUsername(String username)
    {
        this.username = username;
    }

    public String getPassword()
    {
        return password;
    }

    public void setPassword(String password)
    {
        this.password = password;
    }
}

 


package com.zhy.blogcodes.mvp.biz;

/**
 * Created by zhy on 15/6/19.
 */
public interface IUserBiz
{
    public void login(String username, String password, OnLoginListener loginListener);
}

 


package com.zhy.blogcodes.mvp.biz;

import com.zhy.blogcodes.mvp.bean.User;

/**
 * Created by zhy on 15/6/19.
 */
public class UserBiz implements IUserBiz
{

    @Override
    public void login(final String username, final String password, final OnLoginListener loginListener)
    {
        //模拟子线程耗时操作
        new Thread()
        {
            @Override
            public void run()
            {
                try
                {
                    Thread.sleep(2000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                //模拟登录成功
                if ("zhy".equals(username) && "123".equals(password))
                {
                    User user = new User();
                    user.setUsername(username);
                    user.setPassword(password);
                    loginListener.loginSuccess(user);
                } else
                {
                    loginListener.loginFailed();
                }
            }
        }.start();
    }
}

 


package com.zhy.blogcodes.mvp.biz;

import com.zhy.blogcodes.mvp.bean.User;

/**
 * Created by zhy on 15/6/19.
 */
public interface OnLoginListener
{
    void loginSuccess(User user);

    void loginFailed();
}

 

实体类不用说,至于业务类,我们抽取了一个接口,一个实现类这也很常见~~login方法,一般肯定是连接服务器的,是个耗时操作,所以我们开辟了子线程,Thread.sleep(2000)模拟了耗时,由于是耗时操作,所以我们通过一个回调接口来通知登录的状态。

其实这里还是比较好写的,因为和传统写法没区别。

(二) View

上面我们说过,Presenter与View交互是通过接口。所以我们这里需要定义一个ILoginView,难点就在于应该有哪些方法,我们看一眼效果图:

可以看到我们有两个按钮,一个是login,一个是clear;

login说明了要有用户名、密码,那么对应两个方法:


    String getUserName();

    String getPassword();

 

再者login是个耗时操作,我们需要给用户一个友好的提示,一般就是操作ProgressBar,所以再两个:

    void showLoading();

    void hideLoading();

 

login当然存在登录成功与失败的处理,我们主要看成功我们是跳转Activity,而失败可能是去给个提醒:

    void toMainActivity(User user);

    void showFailedError();

 

ok,login这个方法我们分析完了~~还剩个clear那就简单了:

    void clearUserName();

    void clearPassword();

 

综上,接口完整为:

package com.zhy.blogcodes.mvp.view;

import com.zhy.blogcodes.mvp.bean.User;

/**
 * Created by zhy on 15/6/19.
 */
public interface IUserLoginView
{
    String getUserName();

    String getPassword();

    void clearUserName();

    void clearPassword();

    void showLoading();

    void hideLoading();

    void toMainActivity(User user);

    void showFailedError();

}

 

有了接口,实现就太好写了~~~

总结下,对于View的接口,去观察功能上的操作,然后考虑:

  • 该操作需要什么?(getUserName, getPassword)
  • 该操作的结果,对应的反馈?(toMainActivity, showFailedError)
  • 该操作过程中对应的友好的交互?(showLoading, hideLoading)

下面贴一下我们的View的实现类,哈,其实就是Activity,文章开始就说过,MVP中的View其实就是Activity。

package com.zhy.blogcodes.mvp;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.zhy.blogcodes.R;
import com.zhy.blogcodes.mvp.bean.User;
import com.zhy.blogcodes.mvp.presenter.UserLoginPresenter;
import com.zhy.blogcodes.mvp.view.IUserLoginView;

public class UserLoginActivity extends ActionBarActivity implements IUserLoginView
{


    private EditText mEtUsername, mEtPassword;
    private Button mBtnLogin, mBtnClear;
    private ProgressBar mPbLoading;

    private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this);

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_login);

        initViews();
    }

    private void initViews()
    {
        mEtUsername = (EditText) findViewById(R.id.id_et_username);
        mEtPassword = (EditText) findViewById(R.id.id_et_password);

        mBtnClear = (Button) findViewById(R.id.id_btn_clear);
        mBtnLogin = (Button) findViewById(R.id.id_btn_login);

        mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading);

        mBtnLogin.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                mUserLoginPresenter.login();
            }
        });

        mBtnClear.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                mUserLoginPresenter.clear();
            }
        });
    }


    @Override
    public String getUserName()
    {
        return mEtUsername.getText().toString();
    }

    @Override
    public String getPassword()
    {
        return mEtPassword.getText().toString();
    }

    @Override
    public void clearUserName()
    {
        mEtUsername.setText("");
    }

    @Override
    public void clearPassword()
    {
        mEtPassword.setText("");
    }

    @Override
    public void showLoading()
    {
        mPbLoading.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading()
    {
        mPbLoading.setVisibility(View.GONE);
    }

    @Override
    public void toMainActivity(User user)
    {
        Toast.makeText(this, user.getUsername() +
                " login success , to MainActivity", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showFailedError()
    {
        Toast.makeText(this,
                "login failed", Toast.LENGTH_SHORT).show();
    }
}

 

对于在Activity中实现我们上述定义的接口,是一件很容易的事,毕竟接口引导我们去完成。

*后看我们的Presenter。

(三)Presenter

Presenter是用作Model和View之间交互的桥梁,那么应该有什么方法呢?

其实也是主要看该功能有什么操作,比如本例,两个操作:login和clear。

package com.zhy.blogcodes.mvp.presenter;

import android.os.Handler;

import com.zhy.blogcodes.mvp.bean.User;
import com.zhy.blogcodes.mvp.biz.IUserBiz;
import com.zhy.blogcodes.mvp.biz.OnLoginListener;
import com.zhy.blogcodes.mvp.biz.UserBiz;
import com.zhy.blogcodes.mvp.view.IUserLoginView;


/**
 * Created by zhy on 15/6/19.
 */
public class UserLoginPresenter
{
    private IUserBiz userBiz;
    private IUserLoginView userLoginView;
    private Handler mHandler = new Handler();

    public UserLoginPresenter(IUserLoginView userLoginView)
    {
        this.userLoginView = userLoginView;
        this.userBiz = new UserBiz();
    }

    public void login()
    {
        userLoginView.showLoading();
        userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener()
        {
            @Override
            public void loginSuccess(final User user)
            {
                //需要在UI线程执行
                mHandler.post(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        userLoginView.toMainActivity(user);
                        userLoginView.hideLoading();
                    }
                });

            }

            @Override
            public void loginFailed()
            {
                //需要在UI线程执行
                mHandler.post(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        userLoginView.showFailedError();
                        userLoginView.hideLoading();
                    }
                });

            }
        });
    }

    public void clear()
    {
        userLoginView.clearUserName();
        userLoginView.clearPassword();
    }



}

 

注意上述代码,我们的presenter完成二者的交互,那么肯定需要二者的实现类。大致就是从View中获取需要的参数,交给Model去执行业务方法,执行的过程中需要的反馈,以及结果,再让View进行做对应的显示。

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也会相应的作出反应。

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

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速