MVC、MVP、MVVM解析

141 阅读6分钟

前言:

我们开发一个界面大概需要如下几个步骤:

1、 从服务端拉取数据并简单加工和校验

2、 展示基础的页面UI,例如ActionBar、刷新样式、各种动画联动、列表Adaper等等

3、 将数据绑定到界面上,例如绑定一个video对象,或者指定一个User对象,这其逻辑都是很复杂的

4、 对于列表,绑定列表中的每一个item的样式,这部分也是比较复杂的

这些事情该怎么组织,才能让工程的耦合度最低,灵活度最高,有很多方案,我们逐个来探讨

1、MVC架构

M:数据层,负责监听网络接口和数据库接口

V:View,主要指的是res目录下的布局文件,用来显示来源于Model的数据;但activity或者fragment也具有View层的性质

C:controller,主要指的是activity或者fragment,用来处理UI交互以及Model数据的更新

Android原生就是MVC的思路,设计时将activity定位成一个大的Controller,在页面比较简单且都是静态元素的时候是没问题的。但是仔细想,activity如果是Controller,那么Model和View在哪里呢?Model实际上是activity发网络请求得到的(比如点击登录按钮请求网络数据,校验成功后跳转页面),View是activity渲染layout得到的,所以实际上activity本身已经成为了一个超级大包罗万象的东西了,不再是一个只负责逻辑处理的Controller了,因此这些让我们会感觉页面太重了,逻辑复杂,数据流不清晰,耦合度高

问题:activity或者fragment既有View的性质又有Controller的性质,导致activity或者fragment很重,而且View和Model直接交互,耦合度比较高。

2、MVP架构

M:数据层,负责监听网络接口和数据库接口

View:将activity或者fragment看作是veiw,当然也包括静态的布局资源文件

Presenter:相当于中介,负责View和,Model层的交互

为了MVC的问题,诞生了一个叫做MVP的理念,这个理念是将原来的activity里面要做的包络万象的事情,分出来了一部分,这部分就是和界面View相关的业务处理逻辑(注意是含业务处理的,不仅仅是纯绑定),这样分离后,activity负责创建View以及View交互,以及一些无关业务的静态逻辑,并将创建好的View、model还有上下文的环境参数Environment Context都传递给Presenter,由Presenter来负责业务的处理和界面的渲染。并且MVP方案还建议将Model和View都抽离成接口,这样处理的好处有两点:

第一面向接口编程,未来换一种实现会比较简单独立,比如listview换成recyclerview这种事情,第二是将简单的业务逻辑都放在IView和IModel中,比如设置用户头像展示用户名等,这类操作简单通用,可以有效的分离Presenter的大小,使他只专注于复杂的逻辑处理。

以一个简单的登录案例进行分析:

Model层:model层负责拉取服务端数据并校验,校验通过失败都通过Presenter的回调通知给Presenter,这个过程是异步的

public interface LoginModel {

  public void login(final User user, final OnLoginCallBack callBack);

}
public class LoginModelImpl implements LoginModel{
  @Override
  public void login(User user, OnLoginCallBack callBack) {
    final String username = user.getUsername();
    final String password = user.getPassword();
    //省略和服务端校验数据的步骤
    new Handler().postDelayed(new Runnable() {
      @Override public void run() {
        boolean error = false;
        if (TextUtils.isEmpty(username) ||TextUtils.isEmpty(password)){
          callBack.onError();//model层里面回调listener
          error = true;
        }
        
        if (!error){
          callBack.onSuccess();
        }
      }
    }, 2000);
  }
}

View层:接口的方法是供Presenter调用的,在model数据返回之后,无论登陆成功还是失败都要执行相关的UI操作

public interface LoginView {
  //login是个耗时操作,我们需要给用户一个友好的提示
  void showProgress();

  void hideProgress();

  //login当然存在登录成功与失败的处理,失败给出提示
  void ShowError();

  //login成功,也给个提示
  void showSuccess();
}
public class MVPDemoActivity extends AppCompatActivity implements View.OnClickListener, LoginView {

  private TextView mUsername;
  private TextView mPassword;
  private Button mLoginButton;

  private ProgressDialog mProgressDialog;

  private LoginPresenter presenter;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.mvp_login_main_activity);
    initView();
    //创建一个presenter对象,当点击登录按钮时,让presenter去调用model层的login()方法,验证帐号密码
    presenter = new LoginPresenterImpl(this);
  }

  private void initView() {
    mUsername = findViewById(R.id.username);
    mPassword = findViewById(R.id.password);
    mLoginButton = findViewById(R.id.login);
    mLoginButton.setOnClickListener(this);
    mProgressDialog = new ProgressDialog(this);
    mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
    mProgressDialog.setCancelable(false);
    mProgressDialog.setMessage("请稍后");
  }

  @Override
  public void onClick(View v) {
    User user = new User();
    user.setPassword(mPassword.getText().toString());
    user.setUsername(mUsername.getText().toString());
    presenter.login(user);
  }

  @Override
  public void showProgress() {
    mProgressDialog.show();
  }

  @Override
  public void hideProgress() {
    mProgressDialog.dismiss();
  }

  @SuppressLint("ShowToast")
  @Override
  public void ShowError() {
    Toast.makeText(this,"用户名或者密码有误",Toast.LENGTH_SHORT);
  }


  @Override
  public void showSuccess() {
    mProgressDialog.dismiss();
    Toast.makeText(this,"login success",Toast.LENGTH_SHORT).show();
  }
}

P层:持有View和Model层二者的引用,建立起沟通桥梁

public interface LoginPresenter {//model层接校验完数据之后通知Presenter进行后续操作
  void login(User user);
}
public interface LoginPresenter {//由于Presenter要执行View的方法
  void login(User user);
}
public class LoginPresenterImpl implements LoginPresenter, OnLoginCallBack {
  private LoginView loginView;
  private LoginModel loginModel;

  public LoginPresenterImpl(LoginView view){
    loginView = view;
    loginModel = new LoginModelImpl();
  }


  @Override
  public void login(User user) {
    if (loginView != null) {
      loginView.showProgress();
    }

    loginModel.login(user, this);
  }


  @Override
  public void onError() {
    if (loginView != null) {
      loginView.hideProgress();
      loginView.ShowError();
    }
  }

  @Override
  public void onSuccess() {
    if (loginView != null) {
      loginView.showSuccess();
    }
  }
}

总结:

View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。其中Presenter中同时持有View层的Interface的引用以及Model层的引用,而View层持有Presenter层引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的引用,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载情况,最后Presenter层再调用View层的接口将加载后的数据展示给用户。

image.png

3、MVVM架构

MVP架构两个很显著的问题就是:

1、接口太多,导致文件暴增;

2、Presenter类中持有View(activity)的引用,很容易造成内存泄漏

MVVM结构从结构图上来看和MVP差不多,只不过是P变成了VM,但是二者最大的不同就在于,VM和View层的引用时单向持有,而V和P层双向持有引用

image.png

VM和V层通过DataBinding工具类进行绑定。

VM层不会持有activity的引用了,而是持有DataBindingImpl的引用,如果在VM层中对View的属性进行更改,则可以调用VM中的DataBindingImpl.textView.setText("..."),View的数据刷新之后,与之绑定的Model类的相关字段也会进行更新;也可以手动更新Model类的信息

(参考链接:(12条消息) Android MVVM 架构详解_lerendan的博客-CSDN博客_android mvvm的理解

详细使用细节参照:Jetpack:Data Binding入门指南 - 掘金 (juejin.cn)