Android开发中的MVC、MVP与MVVM(有这一篇就够了)

目录

一、介绍

二、无框架示例

2.1  需求(查询用户信息)

2.2  需求整理

2.3  代码实现

三、MVC模型

3.1  MVC模型简介

3.2  MVC代码实现

3.3  MVC的优缺点

四、MVP模型

4.1  MVP模型简介

4.2  MVP代码实现 

4.3  MVP的优缺点

4.4  MVP使用的建议 

五、MVVM模型 

5.1  MVVM模型简介

5.2  DataBinding的基本用法

5.3  MVVM代码实现

5.4  MVVM的优缺点

六、总结


一、介绍

本节讲的主要针对Android开发人员。对于Android开发者肯定会遇到这样一个问题,就是随着项目复杂度的提升,我们的项目将会变得非常难以维护,为了解决这个问题,我们就需要学习Android中常用的一些框架来达到模块内部的高聚合和模块间的低耦合性,提高项目的可维护性和可扩展性。Android中常用的框架主要有三种:MVCMVP以及MVVM。学习框架可以帮助我们优化代码,提高我们日常开发的效率。下面就以理论+代码的方式来依次讲解这三种框架,篇幅有点长:

二、无框架示例

本节我们先做一个小需求,先看一下在不使用任何框架的情况下,怎样通过代码来实现需求,然后在后面的章节再通过使用不同框架实现此需求进行对比。

2.1  需求(查询用户信息)

输入用户名,点击查询按钮查询用户信息,如果查询成功,则将查询成功的数据展示在页面上,如果查询失败,则在页面上提示查询失败。

2.2  需求整理

我们先简要整理下需求,这样看起来会更清晰明了

2.3  代码实现

2.3.1  准备工作

1、首先创建NormalActivity与activity_normal.xml布局文件,并做出基本的初始化,页面效果如下:

代码如下:

activity_normal.xml  页面上主要部分有文本输入框查询按钮显示结果的文本框

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:text="MVC、MVP与MVVM"
        android:gravity="center"
        android:textColor="@color/white"
        android:textStyle="bold"
        android:textSize="18sp"
        android:background="@color/title_bag"/>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="46dp"
        android:layout_margin="20dp">
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:text="用户名:"
            android:textSize="15sp"
            android:textColor="@color/black"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_toRightOf="@+id/tv_name"
            android:layout_marginLeft="15dp"/>
    </RelativeLayout>

    <Button
        android:id="@+id/btn_search"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:text="查询"
        android:background="@color/title_bag"
        android:textColor="@color/white"
        android:textSize="18sp"/>

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:textSize="15sp"
        android:textColor="@color/black"
        android:text="查询结果:"/>

</LinearLayout>

NormalActivity的基本代码如下:

package com.wjy.mdemo.normal;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.wjy.mdemo.R;

/**
 * created by WangJinyong
 */
public class NormalActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_name;//输入要查询的用户名
    private Button btn_search;//查询按钮
    private TextView tv_result;//显示查询结果

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

    //初始化View
    private void initView(){
        et_name = findViewById(R.id.et_name);
        btn_search = findViewById(R.id.btn_search);
        btn_search.setOnClickListener(this);
        tv_result = findViewById(R.id.tv_result);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_search:
                break;
        }
    }
}

2、我们还需要一些准备工作,先封装一个实体类来封装我们的用户信息,在这里我给他起名叫UserInfo,为了方便演示这里只用了三个最基本的信息,姓名:name、性别:sex、年龄:age。

package com.wjy.mdemo.bean;

/**
 * created by WangJinyong
 * 封装用户信息实体类
 */
public class UserInfo {

    private String name;//用户名
    private String sex;//性别
    private int age;//年龄

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

3、还需要一个接口,来通知用户查询的结果,这里起名叫MCallBack。需要提供两个方法:查询成功的方法:onSuccess(UserInfo userInfo)和查询失败的方法:onFailed()。

package com.wjy.mdemo.callback;

import com.wjy.mdemo.bean.UserInfo;

/**
 * created by WangJinyong
 * 通知用户查询结果的接口
 */
public interface MCallBack {

    void onSuccess(UserInfo userInfo);//查询成功
    void onFailed();//查询失败
}

以上准备工作就做完了,下面正式开始做需求部分。

2.3.2  获取用户名

获取的用户名就是我们在输入框输入的内容,我们在NormalActivity里写一个方法获取用户输入的信息getUserInfo()

    /**
     * 获取用户输入的信息,直接返回输入框的信息就可以了
     */
    private String getInputData(){
        return et_name.getText().toString();
    }

2.3.3  获取数据成功

当获取数据成功的时候,展示获取到的用户信息。

    /**
     * 获取数据成功
     */
    private void showSuccess(UserInfo userInfo){
        tv_result.setText("查询结果:\n用户名:"+userInfo.getName()+"\n性别:"+userInfo.getSex()+"\n年龄:"+userInfo.getAge());
    }

2.3.4  获取数据失败

当获取数据失败的时候,直接展示获取数据失败。

    /**
     * 获取数据失败
     */
    private void showFailed(){
        tv_result.setText("查询结果:\n获取数据失败!");
    }

2.3.5  查询用户数据

这个就是获取用户数据的方法,这里需要参数,一个是用户输入的用户名:inputName,另一个就是MCallBack来通知用户查询结果。(注:为了方便,这里就通过模拟的方式来获取用户数据了,实际项目开发中需要通过网络查询或者从数据库中获取数据)

    /**
     * 模拟 获取用户数据(先不通过网络或者数据库查询来获取数据了)
     */
    private void getUserInfo(String inputName, MCallBack mCallBack){
        Random random = new Random();
        boolean isSuccess = random.nextBoolean();//通过Random随机获取师傅成功
        if (isSuccess){
            //如果是成功,设置模拟数据
            UserInfo userInfo = new UserInfo();
            userInfo.setName("张三");
            userInfo.setSex("男");
            userInfo.setAge(25);
            mCallBack.onSuccess(userInfo);//调取成功方法,通知用户,并把模拟数据传递过去
        }else {
            //如果失败  调取失败方法,直接通知用户失败
            mCallBack.onFailed();
        }
    }

2.3.6  业务逻辑

在点击查询按钮查询用户信息

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_search://查询用户信息
                getUserInfo(getInputData(), new MCallBack() {
                    @Override
                    public void onSuccess(UserInfo userInfo) {
                        showSuccess(userInfo);//成功
                    }

                    @Override
                    public void onFailed() {
                        showFailed();//失败
                    }
                });
                break;
        }
    }

以上就实现了我们在前面提出的查询用户信息的需求。在此我们发现了一个问题,就是NormalActivity承担的任务比较多,,比如他既要获取数据,查询数据,又要展示数据(展示成功页面、展示失败页面),还有一部分的逻辑处理(成功的时候展示成功,失败的时候展示失败)。这样写的问题是比较多的,如果我们以后的项目越来越复杂的话,这个NormalActivity的代码会越来越臃肿,非常不好维护,要解决这样的问题,可以使用MVC模式,MVC模式可以很好的对代码进行解耦,将数据的获取和页面的展示分隔开。下一节我们就来看一下MVC模型。 

三、MVC模型

3.1  MVC模型简介

MVC的全名的Model View Controller,即模型(model)-视图(view)-控制器(controller)。下面附上一张MVC比较常见的模型图:

Controller:Activity、Fragment

View:layout、View控件

Model:数据处理(网络请求,SQL等)

箭头代表事件传递方向。

1.将数据的获取与界面的展示分离(将查询用户信息从Activity中分离到Model中即可)

2.解决各层之间的通信问题(Activity通知Model获取数据,Model通知Activity更新界面) 

 3.2  MVC代码实现

1.创建MVCActivity和布局文件activity_mvc.xml,因为布局没有改变直接拷贝上面的布局就可以了,基本控件的初始化也与上面一样,这里就不再写了。

2.根据需求分析,MVCActivity里要实现的获取用户数据、展示成功页面、展示失败页面,上面在NormalActivity里面已经写过了,直接拷贝过来就行。

    /**
     * 获取用户输入的信息,直接返回输入框的信息就可以了
     */
    private String getInputData(){
        return et_name.getText().toString();
    }

    /**
     * 获取数据成功
     */
    private void showSuccess(UserInfo userInfo){
        tv_result.setText("查询结果:\n用户名:"+userInfo.getName()+"\n性别:"+userInfo.getSex()+"\n年龄:"+userInfo.getAge());
    }

    /**
     * 获取数据失败
     */
    private void showFailed(){
        tv_result.setText("查询结果:\n获取数据失败!");
    }

3.还需要新建一个MVCModel类文件,在里面实现查询用户信息功能。(也是直接使用之前写的模拟获取用户数据的方法)

    /**
     * 模拟 获取用户数据(先不通过网络或者数据库查询来获取数据了)
     */
    public void getUserInfo(String inputName, MCallBack mCallBack){
        Random random = new Random();
        boolean isSuccess = random.nextBoolean();//通过Random随机获取师傅成功
        if (isSuccess){
            //如果是成功,设置模拟数据
            UserInfo userInfo = new UserInfo();
            userInfo.setName("张三");
            userInfo.setSex("男");
            userInfo.setAge(25);
            mCallBack.onSuccess(userInfo);//调取成功方法,通知用户,并把模拟数据传递过去
        }else {
            //如果失败  调取失败方法,直接通知用户失败
            mCallBack.onFailed();
        }
    }

到这里就实现了第一步数据的获取与界面的展示分离;接下来就来实现第二步,解决各层之间的通信问题。

package com.wjy.mdemo.mvc;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.wjy.mdemo.R;
import com.wjy.mdemo.bean.UserInfo;
import com.wjy.mdemo.callback.MCallBack;

/**
 * created by WangJinyong
 */
public class MVCActivity extends AppCompatActivity implements View.OnClickListener {

    ……
    private MVCModel mvcModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ……
        mvcModel = new MVCModel();//对MVCModel初始化
    }

    //初始化View
    private void initView(){
        ……
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_search://查询用户信息
                mvcModel.getUserInfo(getInputData(), new MCallBack() {
                    @Override
                    public void onSuccess(UserInfo userInfo) {
                        showSuccess(userInfo);//成功
                    }

                    @Override
                    public void onFailed() {
                        showFailed();//失败
                    }
                });
                break;
        }
    }
}

3.3  MVC的优缺点

优点:

一定程度上实现了Model与View的分离,降低了代码耦合性。

缺点:

Controller与View难以完全解耦,并且随着项目复杂度的提升,Controller将越来越臃肿。

四、MVP模型

 4.1  MVP模型简介

MVP即Model-View-Presenter模型

MVP与MVC的差别:

1.Model与View不再直接进行通信,而是通过中间层Presenter来实现。

2.Activity的功能被简化,不再充当控制器,主要负责View层面的工作。

1.MVPActivity负责提供View层面的功能(采用实现接口的方式)

2.MVPModel负责提供数据方面的功能

3.Model与View不再直接通信,通过Presenter来实现 ,让Presenter持有View和Model的引用

4.2  MVP代码实现 

1.创建MVPActivity和布局文件activity_mvp.xml,因为布局没有改变直接拷贝上面的布局就可以了,基本控件的初始化也与上面一样,这里就不再写了。

2.提供对应的视图功能,需要创建一个接口IMVPView,在接口中声明对应的视图功能:获取用户输入、展示成功界面、展示失败界面。

package com.wjy.mdemo.mvp;

import com.wjy.mdemo.bean.UserInfo;

/**
 * created by WangJinyong
 * 提供视图功能的接口
 */
public interface IMVPView {

    /**
     * 获取用户输入的信息
     */
    String getInputData();

    /**
     * 获取数据成功
     */
    void showSuccess(UserInfo userInfo);

    /**
     * 获取数据失败
     */
    void showFailed();
}

3.在MVPActivity里实现IMVPView接口里面功能

package com.wjy.mdemo.mvp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.wjy.mdemo.R;
import com.wjy.mdemo.bean.UserInfo;

/**
 * created by WangJinyong
 */
public class MVPActivity extends AppCompatActivity implements View.OnClickListener,IMVPView {

    ……

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ……
    }

    //初始化View
    private void initView(){
        ……
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_search://查询用户信息
                break;
        }
    }

    @Override
    public String getInputData() {
        return et_name.getText().toString();
    }

    @Override
    public void showSuccess(UserInfo userInfo) {
        tv_result.setText("查询结果:\n用户名:"+userInfo.getName()+"\n性别:"+userInfo.getSex()+"\n年龄:"+userInfo.getAge());
    }

    @Override
    public void showFailed() {
        tv_result.setText("查询结果:\n获取数据失败!");
    }
}

4.还需要新建一个MVPModel类文件,在里面实现查询用户信息功能。(直接拷贝MVCModel里面的内容就行了)到此数据层面的功能就完成了。

5.还需要一个中间层将他们关联起来。创建一个MVPPresenter(持有View和Model层的引用)

package com.wjy.mdemo.mvp;

import com.wjy.mdemo.bean.UserInfo;
import com.wjy.mdemo.callback.MCallBack;

/**
 * created by WangJinyong
 * 中间关联层   持有View和Model层的引用
 */
public class MVPPresenter {

    private IMVPView imvpView;
    private MVPModel mvpModel;

    public MVPPresenter(IMVPView imvpView) {
        this.imvpView = imvpView;
        mvpModel = new MVPModel();
    }

    //获取数据
    public void getData(String inputName){
        mvpModel.getUserInfo(inputName, new MCallBack() {
            @Override
            public void onSuccess(UserInfo userInfo) {
                imvpView.showSuccess(userInfo);//成功
            }

            @Override
            public void onFailed() {
                imvpView.showFailed();//失败
            }
        });
    }
}

6.点击按钮通过调用MVPPresenter里的查询获取用户信息方法获取用户信息。

public class MVPActivity extends AppCompatActivity implements View.OnClickListener,IMVPView {
    ……
    private MVPPresenter mvpPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvp);
        mvpPresenter = new MVPPresenter(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_search://查询用户信息
                mvpPresenter.getData(getInputData());
                break;
        }
    }
}

4.3  MVP的优缺点

优点:

解决了MVC中Controller与View的过渡耦合的缺点,职责划分明显,更加易于维护。

缺点:

接口数量多,项目复杂度升高。随着项目复杂度的提升,Presenter层将越来越臃肿。

4.4  MVP使用的建议 

1.接口规范化(封装父类接口以减少接口的使用量)

2.使用第三方插件自动生成MVP代码

3.对于一些简单的页面,可以选中不使用框架

4.根据项目复杂程度,部分模块可以选中不使用接口 

五、MVVM模型 

5.1  MVVM模型简介

 

在这里我们发现MVVM的模型图和MVP的模型图是非常相似的,唯一的区别就是将Presenter替换成了ViewModel。MVVM和MVP在思想上是非常接近的,但是在代码和逻辑上MVVM会显得更加简洁。

MVVM是Model-View-ViewModel的简写,MVVM在MVP的基础上实现了数据视图的绑定(DadaBinding),当数据变化时,视图会自动更新;反之,当视图发生变化时,数据也会自动更新。相对于MVP减少了接口数量、告别繁琐的findViewById操作。 

5.2  DataBinding的基本用法

5.2.1  DataBinding是什么

DataBinding是谷歌官方发布的一个实现数据绑定的框架(实现数据与视图的双向绑定),DataBinding可以帮助我们在Android中更好的实现MVVM模式。

5.2.2  DataBinding使用步骤

1.启用DataBinding

在app的build.gradle的android下添加下面这句,这样项目就支持DataBinding 了。

android {
    ……

    dataBinding {
        enabled = true
    }
}

2.修改布局文件为DataBinding布局

选中布局文件的最外层,按住Alt+Enter键,选择 Convert to data binding layout。如下图所示:   

这样,布局就变成了data binding布局了,我们发现布局的最外层已经变成了layout。

当布局转化成data binding布局后,系统会为我们自动生成一个类Activity**Binding(**为我们创建的类名) 。我这里写的例子的类名为DataBindingActivity,因此自动生成的类名为ActivityDataBindingBinding。

使用DataBinding的话,我们的setContentView也需要做一下改变,需要使用DataBindingUtil.setContentView,这样就可以直接使用databinding直接使用控件,免去了繁琐的findViewById操作。

public class DataBindingActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityDataBindingBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_data_binding);
        binding.tvData.setText("");//直接使用,避免繁琐的findViewById
    }
}

3.绑定数据

在布局的data中我们可以声明对象,例如我们用到UserInfo数据,那么就声明变量userInfo,以及类型。

    <data>
        <variable
            name="userInfo"
            type="com.wjy.mdemo.bean.UserInfo" />
    </data>

下面就可以在布局中直接使用userInfo,比如在文本框中使用,就像下面这样使用:

android:text="@{userInfo.name+'|'+userInfo.age}"

绑定数据

    private UserInfo userInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityDataBindingBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_data_binding);
        userInfo = new UserInfo();
        userInfo.setName("张三");
        userInfo.setAge(25);
        binding.setUserInfo(userInfo);
    }

这样就能将数据展示在页面上了,这些工作都是DataBinding帮我们做的,因为我们上面在文本框中引用了userInfo的属性。

下面我们再添加一个功能:点击按钮的时候将年龄(age)加1。这样我们需要给按钮添加点击事件。因此我们在data下再引入一个对象

    <data>
        ……
        <variable
            name="activity"
            type="com.wjy.mdemo.databinding.DataBindingActivity" />
    </data>

然后为按钮设置点击事件android:onClick="@{activity.onClick}"  同时需要在Activity里提供这个onClick方法,同时需要初始化一下activity

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ……
        binding.setActivity(this);
    }

    public void onClick(View view){}

这里提醒一下:如果在绑定activity的时候找不到.setActivity方法的话,clean一下project就好了,这是由于DataBinding的编译问题。

下面再onClick方法里实现点击年龄加1。

    public void onClick(View view){
        Toast.makeText(this,"点击了按钮",Toast.LENGTH_SHORT).show();
        int age = userInfo.getAge();
        userInfo.setAge(age+1);
        binding.setUserInfo(userInfo);
    }

这时我们会发现,仅仅改变数据,视图也是会更新的,这就是DataBinding帮我们做的。

我们还可以优化一下,因为每次都要调用binding.setUserInfo()方法。优化的话,我们可以这样写,我们用到的UserInfo数据类,让它继承BaseObservable;我们希望年龄在发生变化的时候自动更新视图,就在getAge()方法上面加一个注解@Bindable,同时在setAge方法里更新年龄notifyPropertyChanged(BR.age);  这里的BR是DataBinding生成的,和我们平常使用的R的功能比较相似。这时在点击事件里的binding.setUserInfo绑定就可以去掉了。这样我们就实现了当用户年龄发生变化的时候自动更新视图。

package com.wjy.mdemo.bean;

import android.databinding.BaseObservable;
import android.databinding.Bindable;

import com.wjy.mdemo.BR;

/**
 * created by WangJinyong
 * 封装用户信息实体类
 */
public class UserInfo extends BaseObservable {

    ……

    @Bindable
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
        notifyPropertyChanged(BR.age);
    }
}
    public void onClick(View view){
        Toast.makeText(this,"点击了按钮",Toast.LENGTH_SHORT).show();
        int age = userInfo.getAge();
        userInfo.setAge(age+1);
//        binding.setUserInfo(userInfo);
    }

这样我们的功能就实现了,当我们改变数据的时候,视图自动更新。需要注意的一点是,到目前为止,我们的数据绑定都是单向绑定的(就是数据改变,视图自动更新)。

DataBinding也支持双向绑定,下面简单说一下双向绑定。就拿刚才的按钮点击事件来说,在@后面加一个等号(=)一般就代表双向绑定。在视图更新的时候也会自动更新数据。双向绑定一般用于输入框。对于DataBinding的使用,功能比较多,这里就不多说了,可以到DataBinding的官方网站学习。

android:onClick="@={activity.onClick}"

5.3  MVVM代码实现

1.提供View、ViewModel以及Model三层

新建三个文件,MVVMActivity(页面布局跟之前的一样,直接拷贝过来就行)、MVVMViewModel(创建一个构造器)、MVVMModel(跟MVCModel一样,直接拷贝过来);

package com.wjy.mdemo.mvvm;

import android.app.Application;

public class MVVMViewModel {

    /**
     * 一般需要传入Application对象,方便在ViewModel中使用application
     * 比如SharedPreferences需要使用
     * @param application
     */
    public MVVMViewModel(Application application){}
}

2.将布局修改为DataBinding布局

参考上面5.2.2  DataBinding的使用步骤,这里就不再说了。

3.View与ViewModel之间通过DataBinding进行通信

首先在布局文件中声明ViewModel

    <data>
        <variable
            name="viewModel"
            type="com.wjy.mdemo.mvvm.MVVMViewModel" />
    </data>

然后在Activity中创建MVVMViewModel对象并初始化

        MVVMViewModel mvvmViewModel = new MVVMViewModel(getApplication());
        binding.setViewModel(mvvmViewModel);

 之后给按钮添加点击事件,在点击按钮时候调用ViewModel中的getData方法,android:onClick="@{viewModel.getData}",就是将按钮与ViewModel中的getData方法绑定,因此需要在ViewModel中提供getData方法,这样在点击按钮的时候就会调用ViewModel中的getData方法。

public void getData(View view){ }

将文本框的展示信息与 ViewModel中的result绑定android:text="@{viewModel.result}",因此需要在ViewModel提供result字段。 

public class MVVMViewModel extends BaseObservable {

    private String result;

    ……

    @Bindable
    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
        notifyPropertyChanged(BR.result);
    }
}

4.获取数据并展示在界面上 

点击按钮调取MVVMViewModel里点击事件getData方法,使用binding获取数据。

/**
 * created by WangJinyong
 * 业务逻辑处理
 * 数据更新
 */
public class MVVMViewModel extends BaseObservable {

    private ActivityMvvmBinding binding;
    private MVVMModel mvvmModel;
    ……
    /**
     * getData方法里mvvmModel.getUserInfo需要获取inputName,所以这里再写一个构造方法,把ActivityMvvmBinding传过来,通过binding获取输入框数据
     * @param application
     * @param binding
     */
    public MVVMViewModel(Application application, ActivityMvvmBinding binding){
        mvvmModel = new MVVMModel();
        this.binding = binding;
    }

    public void getData(View view){
        String inputName = binding.etName.getText().toString();
        mvvmModel.getUserInfo(inputName, new MCallBack() {
            @Override
            public void onSuccess(UserInfo userInfo) {
                String info = "查询结果:\n"+userInfo.getName()+"|"+userInfo.getAge();
                setResult(info);
            }

            @Override
            public void onFailed() {
                setResult("查询结果:\n获取信息失败!");
            }
        });
    }
}

上面是在ViewModel里使用binding获取数据,我们还可以做下改进。就是将输入框与ViewModel中的一个数据绑定(双向绑定)。android:text="@={viewModel.userInput}"。和上面result一样,在ViewModel提供userInput字段。这样当输入框内容发生变化的时候,就会自动更新页面,就不需要binding获取数据了。

/**
 * created by WangJinyong
 * 业务逻辑处理
 * 数据更新
 */
public class MVVMViewModel extends BaseObservable {

    private String userInput;

    public void getData(View view){
        mvvmModel.getUserInfo(userInput, new MCallBack() {
            @Override
            public void onSuccess(UserInfo userInfo) {
                String info = "查询结果:\n"+userInfo.getName()+"|"+userInfo.getAge();
                setResult(info);
            }

            @Override
            public void onFailed() {
                setResult("查询结果:\n获取信息失败!");
            }
        });
    }

    @Bindable
    public String getUserInput() {
        return userInput;
    }

    public void setUserInput(String userInput) {
        this.userInput = userInput;
        notifyPropertyChanged(BR.userInput);
    }
}

这样在ViewModel里就用不到binding了,可以把binding去掉,实现更完全的解耦。

最后还有一点需要说明的是,我们的View可以向ViewModel传递信息,,比如当点击的时候可以调用ViewModel的getData方法;ViewModel也可以通过改变数据通知视图自动更新。但是有些功能我们需要放在Activity中去做,这样就会出现问题,比如我们需要在Activity中,当点击按钮的时候,需要在Activity中进行一些申请权限的操作,这个时候ViewModel要如何通知Activity去做这样一件事呢?要实现这个功能可以直接让ViewModel持有Activity的引用,或者是借助第三方库,比如EventBus,这样做的话都不够好。对于MVVM使用LiveData+ViewModel的方式实现比较好。这样做的原因如下:

  • LiveData是一个可以被观察的数据持有者,它可以通过添加观察者的方式来让其他组件观察他的变更。
  • LiveData遵从应用程序的声明周期(如果LiveData的观察者已经是销毁状态,LiveData就不会通知该观察者。)

5.4  MVVM的优缺点

优点:

实现了数据和视图的双向绑定,极大简化代码。

缺点:

bug难以调试,并且dataBinding目前还存在一些编译问题。

六、总结

MVC:学习简单但是不够彻底

MVP:解耦更加彻底,学习起来也相对比较简单,但是代码相对比较繁琐。

MVVM:代码逻辑非常简洁,但是学习成本较大

至于如何选择框架模型,我们需要根据自己的项目选择最合适自己的框架,如果有些界面或者项目比较简单,我们就可以不使用框架或者使用MVC框架,项目如果比较复杂,需要频繁的维护的话,需要在MVP或者MVVM中进行选择。 这篇文章到此就结束了,有不足之处欢迎大家多多指正,我们共同探讨交流。

下面附上demo地址:

CSDN下载地址:https://download.csdn.net/download/u013184970/11976091

gitee下载地址:https://gitee.com/AlaYu/mvc_mvp_and_mvvm

发布了92 篇原创文章 · 获赞 38 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/u013184970/article/details/103028693