Android MVC,MVP和MVVM架构模式的探究

Android MVC,MVP和MVVM架构模式的探究

八归少年 2,978 2020-03-23

首语

  • Android项目开发中,尤其大型的项目中,模块内部的高聚合和模块间的低耦合显得很是重要。为此我们需要选择一种框架模式,Android通常使用到的有MVC,MVP和MVVM。通过框架模式设计的项目能够极大提高开发效率,提高项目的可维护性和可扩展性,同时在模块测试和Bug处理上也有很大便利。

需求(查询用户账号信息)

  • 用户输入账号,点击按钮可进行查询账号信息,如果查询数据成功,则将数据展示在页面上;如果查询失败,则在页面上提示获取数据失败。

不使用框架

  • Activity主要实现的功能有获取用户输入的信息、展示获取信息成功页面、展示获取信息失败页面、查询用户数据和业务逻辑。
	private String getUserInput(){//获取用户输入的信息
        return etinput.getText().toString().trim();
    }

    private void showSuccessPage(Account account){//展示获取信息成功页面
        tvinformation.setText("用户账号:"+account.getName()+"用户等级:"+account.getLevel());
    }

    private void showFailedPage(){//展示获取信息失败页面
        tvinformation.setText("获取数据失败");
    }

    private void getAccountData(String accountName, MCallBack callBack){//查询用户数据
        Random random = new Random();
        boolean isSuccess = random.nextBoolean();
        if (isSuccess){
            Account account = new Account();
            account.setName(accountName);
            account.setLevel(100);
            callBack.onSuccess(account);
        }else {
            callBack.onFailed();
        }
    }
     @Override
    public void onClick(View view) {
        String userInput = getUserInput();
        getAccountData(userInput, new MCallBack() {
            @Override
            public void onSuccess(Account account) {
                showSuccessPage(account);
            }

            @Override
            public void onFailed() {
                showFailedPage();
            }
        });
    }
    
    public interface MCallBack {

    void onSuccess(Account account);
    void onFailed();
}

	//Account Bean类
	private String name;//姓名
    private int level;//等级

    public String getName() {
        return name;
    }

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

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

MVC

  • MVC的全名是Model-View-Controller,是模型(Model)-视图(View)-控制器(Controller)的缩写。Model层负责数据处理(网络请求,SQL等),View层负责Layout、View控件。Controller层负责Activity,Fragment。
需求分析
  • Controller层:业务逻辑处理,获取用户输入,展示成功页面,展示失败页面。
  • Model层:查询账号数据。
  • View层:layout布局。
实现
  • 1.将数据的获取与界面的展示分离 。
  • 2.解决各层之间通信问题(Activity通知Model获取数据,Model通知Activity更新页面)。
//Model层
public void getAccountData(String accountName, MCallBack callBack){
        Random random = new Random();
        boolean isSuccess = random.nextBoolean();
        if (isSuccess){
            Account account = new Account();
            account.setName(accountName);
            account.setLevel(100);
            callBack.onSuccess(account);
        }else {
            callBack.onFailed();
        }
    }
//Controller层
	private String getUserInput(){
        return etinput.getText().toString().trim();
    }
    private void showSuccessPage(Account account){
        tvinformation.setText("用户账号:"+account.getName()+"用户等级:"+account.getLevel());
    }

    private void showFailedPage(){
        tvinformation.setText("获取数据失败");
    }
    @Override
    public void onClick(View view) {
       mMvcModel.getAccountData(getUserInput(), new MCallBack() {
           @Override
           public void onSuccess(Account account) {
               showSuccessPage(account);
           }

           @Override
           public void onFailed() {
               showFailedPage();
           }
       });
    }    
MVC优缺点
  • 优点:一定程度上实现了Model与View的分离,降低了代码的耦合性。
  • 缺点:Controller与View难以完全解耦,并且随着项目复杂度的提升,Controller将越来越臃肿。

MVP

  • MVP的全名是Model-View-Presenter,是模型(Model)-视图(View)-中间层(Presenter)的缩写。Model层负责数据处理(网络请求、SQL等) ,View层负责用户交互(Activity,Fragment),Presenter层作为View层与Model层中间的纽带,负责处理与用户交互的主要业务逻辑。
需求分析
  • Model层:查询账号数据。
  • View层:获取用户输入、展示成功界面、展示失败界面。
  • Presenter层:业务逻辑处理。
实现
  • 1.Activity负责提供View层面的功能(采用实现接口的方式)
  • 2.Model层负责提供数据方面的功能。
  • Model层与View层不再直接通信,通过Presenter来实现。
//Model层负责提供数据方面的功能
public void getAccountData(String accountName, MCallBack callBack){
        Random random = new Random();
        boolean isSuccess = random.nextBoolean();
        if (isSuccess){
            Account account = new Account();
            account.setName(accountName);
            account.setLevel(100);
            callBack.onSuccess(account);
        }else {
            callBack.onFailed();
        }
    }
//采用实现接口的方式实现View层面的功能
    public interface MVPView {

    String getUserInput();
    void showSuccessPage(Account account);
    void showFailedPage();
}
//中间层Presenter
private MVPView mMVPView;
    private MVPModel mMVPModel;

    public MVPPresenter(MVPView mMVPView) {
        this.mMVPView = mMVPView;
        mMVPModel = new MVPModel();
    }
    public void getData(String accountName){
        mMVPModel.getAccountData(accountName, new MCallBack() {
            @Override
            public void onSuccess(Account account) {
                mMVPView.showSuccessPage(account);
            }

            @Override
            public void onFailed() {
                mMVPView.showFailedPage();
            }
        });
    }
//Activity中Model与View不再直接通信,通过Presenter来实现。继承View接口层   
	@Override
    public void onClick(View view) {
        mMVPPresenter.getData(getUserInput());
    }

    @Override
    public String getUserInput() {
        return etinput.getText().toString().trim();
    }

    @Override
    public void showSuccessPage(Account account) {
        tvinformation.setText("用户账号:"+account.getName()+"用户等级:"+account.getLevel());
    }

    @Override
    public void showFailedPage() {
        tvinformation.setText("获取数据失败");
    }
MVP优缺点
  • 优点:解决了MVC中Controller与View过度耦合的的缺点,职责划分明显,更加易于维护。
  • 缺点:接口数量多,项目复杂度提高,随着项目复杂度的提高,Presenter将越来越臃肿。
建议
  • 1.接口规范化(封装父类接口以减少接口的使用量)

  • 2.使用MVP插件自动生成MVP的代码

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

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

MVC与MVP的区别
  • 1.Model与View不再直接进行通信,而是通过中间层Presenter来实现。
  • 2.Activity的功能被简化,不再充当控制器,主要负责View层面的工作。
PS(常见MVP插件)

MVVM

  • MVVM的全名是Model-View-ViewModel,是模型(Model)-视图(View)-数据视图管理器(ViewModel)的缩写。Model层负责数据处理,View层负责Activity,Fragment。VIewModel层负责调用Model,拿到数据更新自身,而View与ViewModel双向绑定,所以View会自动更新。MVVM在MVP的基础上实现了数据视图的绑定(DataBinding),当数据变化时,视图会自动更新;反之,视图发生变化时,数据也会自动更新。
DataBinding
使用步骤

1.启用DataBinding

//编辑app目录下的build.gradle文件,添加下面的内容。
android {
    ...
    dataBinding {
        enabled = true
    }
}

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

  • 选中布局文件根布局(如LinearLayout),按alt+enter,点击Convert to data binding layout,
    布局文件转化为DataBinding布局。它最外层是layout,里面包含data和我们的原始布局。
  • 在data中我们可以声明对象,然后我们就可以在布局文件中使用对象。@={}表示双向绑定(具体见代码)
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="account"
            type="com.yhj.framedemo.bean.Account" />

        <variable
            name="activity"
            type="android.app.Activity" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".databinding.DemoActivity">

        <TextView
            android:id="@+id/tv_level"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:text="@{account.name+'|'+account.level}" />

        <Button
            android:id="@+id/bt_level"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:onClick="@{activity.onClick()}"
            android:text="等级+1" />

    </LinearLayout>
</layout>
  • 生成DataBinding布局以后,setContentView(view)也要发生改变,使用DataBindingUtil.setContentView,同时使用binding省略了findViewById。
  ActivityMvvmBinding binding = 
  DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
  binding.etInput.setText("省略findViewById()");

3.数据绑定

  • 让Bean类Account继承BaseObservable,为了使数据发生变化视图自动更新,在getLevel()上加注解Binable,在setLevel()中notifyPropertyChanged(BR.level)。
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_demo);
        account = new Account();
        account.setName("Tom");
        account.setLevel(100);
        binding.setAccount(account);
        binding.setActivity(this);
    }

    public void onClick(View view){
        Toast.makeText(this, "点击了!", Toast.LENGTH_SHORT).show();
        int level = account.getLevel();
        account.setLevel(level+1);
	    //binding.setAccount(account);
    }
public class Account extends BaseObservable {

    private String name;//姓名
    private int level;//等级
    
    public String getName() {
        return name;
    }

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

    @Bindable
    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
        notifyPropertyChanged(BR.level);
    }
}
需求分析
  • Model层:查询账号数据。
  • View层:获取用户输入,展示成功界面,展示失败界面。
  • ViewModel层:业务逻辑处理,数据更新。
实现
  • 提供View,ViewModel以及Model三层。
  • 将布局修改为DataBinding布局
  • View与ViewModel之间通过DataBinding进行通信。
  • 获取数据展示在界面上。
//首先是DataBinding布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="viewModel"
            type="com.yhj.framedemo.mvvm.MVVMViewModel"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".mvvm.MVVMActivity">

        <EditText
            android:hint="请输入要查询的账号"
            android:text="@={viewModel.userInput}"
            android:layout_marginTop="20dp"
            android:id="@+id/et_input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <Button
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:id="@+id/bt_get"
            android:onClick="@{viewModel.getData}"
            android:text="获取账号信息"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:id="@+id/tv_information"
            android:text="@{viewModel.result}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</layout>
//Model层数据处理
public void getAccountData(String accountName, MCallBack callBack){
        Random random = new Random();
        boolean isSuccess = random.nextBoolean();
        if (isSuccess){
            Account account = new Account();
            account.setName(accountName);
            account.setLevel(100);
            callBack.onSuccess(account);
        }else {
            callBack.onFailed();
        }

    }
//ViewModel层
public class MVVMViewModel extends BaseObservable {
    private String result;
    private String userInput;

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

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

    private final MVVMModel mvvmModel;
    private MVVMModel mvvmModel1;
    private ActivityMvvmBinding binding;

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

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

    //一般需要传入Application对象,方便在ViewModel中使用Application
    //比如SharedPreference
    public MVVMViewModel(Application application) {
        mvvmModel = new MVVMModel();
    }

    public void getData(View view) {
        mvvmModel.getAccountData(userInput, new MCallBack() {
            @Override
            public void onSuccess(Account account) {
                String info = account.getName() + "|" + account.getLevel();
                setResult(info);
            }

            @Override
            public void onFailed() {
                setResult("获取失败!!!");
            }
        });
    }
}
//View层Activity
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMvvmBinding binding =
                DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        MVVMViewModel mvvmViewModel = new MVVMViewModel(getApplication());

        binding.setViewModel(mvvmViewModel);

    }
  • View可以向ViewModel传递信息,ViewModel改变数据通知View自动更新,但是有些功能我们要在Activity中实现(比如点击按钮获取应用权限),ViewModel如何通知View呢?要实现这个功能,我们可以让ViewModel执有Activity的引用,或者借用EventBus。对于MVVM模式来说,我们可以使用LiveData+ViewModel来实现。
LiveData
MVVM优缺点
  • 优点:实现了数据与视图的双向绑定,极大的简化代码。
  • 缺点:bug难以调试,并且DataBinding目前还存在一些编译问题。

总结

  • Android开发中,一直在用MVC,甚至可以说没有使用,一些大型项目随着不断的迭代,出现了许多问题,推出了MVP,再到现在主推的MVVM。架构演进代码质量也更高,但学习难度也增大。
  • Android开发语言,一直在用Java,如今又推出了Kotlin语言,已经成为Android官方支持开发语言。以及跨平台开发方案Flutter使用的Dart语言,想想哇,Android之路漫漫啊,还有许多东西要学习使用。需要一直提升自己,做未来的T型人才
  • 项目源码:FrameDemo

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.yanghujun.com/archives/architecturemode