MVP

作者: 四喜汤圆 | 来源:发表于2019-07-20 00:10 被阅读0次

本文是对Android MVP 的简单介绍与使用的学习笔记,对着博客实现了相应代码。

一、作用

Model-View-Presenter

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

二、相关概念

1. MVC

(1)模型说明

  • M(实体类和业务逻辑)
  • V(只对应布局文件)
  • C(Activity)

当 View 需要更新数据时去找 Controller,Controller 去找 Model,Model 获取数据后直接更新 View。

(2)MVC 模式的缺点

  • View 和 Controller 没有分离,耦合度高,逻辑混乱

  • Activity 臃肿,代码量大。View 只对应布局文件,能做的事情特别少,布局文件中数据的绑定,事件的处理都在 Activity 中完成,所以Activity 即像 View,又像 Controller

2. MVP

(1)模型说明

  • M(实体类和业务逻辑)
  • V(Activity,负责 View 的绘制及与用户的交互)
  • P(Presenter,是 M 和 V 交互的桥梁)

V 需要更新数据时去找 P,P 去找 M 获取数据,M 请求到数据后通知 P,P 再通知 V 去更新数据。

(2)MVP 模式的特点

  • 职责清晰
  • 耦合度低,便于测试

三、使用

1. 为 M、V、P 分别创建对应接口

  • M 层
public interface LoginModel {
    
}
  • V 层
public interface LoginView {

}
  • P 层
public interface LoginPresenter {
    
}

这个时候创建的接口里面都还是空空如也。

2. 创建 M、V、P 对应接口的实现类,并传入相应引用

  • M:该层的方法中传入结果返回后在 P 层的回调接口
public class LoginModelImpl implements LoginModel {
    
}

M 层方法中传入一个回调接口,M 获取数据后通过回调接口来通知 P,这个在后面的步骤中体现。

  • V:中持有 P 层的引用
public class LoginActivity extends AppCompatActivity implements LoginView {
    // V中持有P的引用
    private LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLoginPresenter = new LoginPresenterImpl(this);
    }

}

此处在onCreate()中通过new LoginPresenterImpl(this)创建了实例。

  • P:持有 M 和 V 的引用
public class LoginPresenterImpl implements LoginPresenter, OnLoginFinishedListener {
    private LoginModel mLoginModel;
    private LoginView mLoginView;

    // 通过构造函数将引用传入
    public LoginPresenterImpl(LoginView loginView) {
        mLoginView = loginView;
        mLoginModel = new LoginModelImpl();
    }

}

此处从 P 的构造函数中传入 M、V 的实例。

3. 从需求 V 出发,一层一层地往下写

本文设想的是登录逻辑,用户输入账号、密码后点击『登录』按钮,验证是否可以登录成功。

点击登录按钮,获取填写的用户名和密码,传入后台进行验证。

V 层中需要更新数据时去找 P ,然后 P 找 M,M 获取到数据后通知 P,P 再通知 V 更新数据。这样 Model 和 View 就不会直接交互了,所有的交互都由 Presenter 进行,Presenter 充当了桥梁的角色。

private LoginPresenter mPresenter;
private EditText mEtUsername;
private EditText mEtPassword;
private Button mBtnLogin;
private ProgressBar mProgressBar;

// V中持有P的引用
private LoginPresenter mLoginPresenter;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);
    mEtUsername = findViewById(R.id.username);
    mEtPassword = findViewById(R.id.password);
    mBtnLogin = findViewById(R.id.login_btn);
    mProgressBar = findViewById(R.id.pb);

    mLoginPresenter = new LoginPresenterImpl(this);

    mBtnLogin.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            User user = new User();
            user.setPassword(mEtPassword.getText().toString());
            user.setUsername(mEtUsername.getText().toString());
            mLoginPresenter.validateCredentials(user);
        }
    });
}

V 找 P:V 想要更新数据的时候去找 P,即mLoginPresenter.validateCredentials(user);,根据该行代码,在LoginPresenter接口中生成void validateCredentials(User user);方法,然后在LoginPresenterImpl实现类中实现该方法。

LoginPresenterImpl.java
@Override
public void validateCredentials(User user) {
    if (mLoginView != null) {
        // 显示进度条
        mLoginView.showProgress();
    }
    mLoginModel.login(user, this);
}

P 找 M:mLoginModel.login(user, this);为P 层找 M 层获取数据。那么,LoginModel接口中生成void login(User user, OnLoginFinishedListener loginPresenter);方法,然后在LoginModelImpl实现类中实现该方法。

LoginModelImpl.java
@Override
public void login(final User user, final OnLoginFinishedListener listener) {
    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)) {
                listener.onUsernameError();
                error = true;
            }
            if (TextUtils.isEmpty(password)) {
                listener.onPasswordError();
                error = true;
            }
            listener.onSuccess();
        }
    }, 2000);

}

M 回调 P:M 找到数据后,调用 P 层中的回调方法listener.onUsernameError();listener.onPasswordError();listener.onSuccess();将数据加载情况反应给 P。

P 实现回调接口OnLoginFinishedListener

public class LoginPresenterImpl implements LoginPresenter, OnLoginFinishedListener {
    @Override
    public void onUsernameError() {
        if (mLoginView != null) {
            mLoginView.setUsernameError();
            mLoginView.hideProgress();
        }
    }

    @Override
    public void onPasswordError() {
        if (mLoginView != null) {
            mLoginView.setPasswordError();
            mLoginView.hideProgress();
        }
    }

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

P 通知 M:P 通知 M 更新数据。

public class LoginActivity extends AppCompatActivity implements LoginView {
    @Override
    public void showProgress() {
        mProgressBar.setVisibility(View.VISIBLE);
    }

    @Override
    public void setUsernameError() {
        mEtUsername.setError("姓名错误");
    }

    @Override
    public void hideProgress() {
        mProgressBar.setVisibility(View.GONE);
    }

    @Override
    public void setPasswordError() {
        mEtPassword.setError("密码错误");
    }

    @Override
    public void showSuccess() {
        hideProgress();
        Toast.makeText(this, "成功了", Toast.LENGTH_SHORT).show();
    }
}

4. 按照从上到下的顺序依次写其他需求

三、注意

1. 接口的必要性

接口是为了实现代码的复用。试想,在 P 中持有 M、V 的引用,如果直接把 Activity 传入 P 中的话,那么这个 P 只能为这一个 Activity 服务。

假设手机 App 已经开发完毕,现在要在平板上进行适配,要把原来的 Activity 替换成 Fragment 。

  • 如果没有 View 的接口的话
    那么就还需要写一个针对该 Fragment 的 P

  • 如果有 View 接口的话
    直接让 Fragment 实现该接口即可,P、M 都不需改动

2. 防止内存泄露

P 中持有 Activity 的引用,假设在 P 调用 M 请求数据的时候 Activity 退出了,但由于 P 正在进行耗时操作,导致 Activity 无法回收,从而内存泄露。

所以在 Activity 退出后还应进行资源回收操作,如 Handler 的任务取消等。

LoginActivity.java
@Override
protected void onDestroy() {
    super.onDestroy();
    mLoginPresenter.onDestroy();
}
LoginPresenterImpl.java
@Override
public void onDestroy() {
    mLoginView = null;
}

参考文献

Android MVP 的简单介绍与使用
Android MVP架构的自述
一个小例子彻底搞懂 MVP

相关文章

  • Android MVP

    Android MVP初探 Android MVP进阶 Android MVP高级 Android MVP扩展

  • MVP / RxJava / Retrofit / RxBus

    MVP Android MVP 详解(上) Android MVP 详解(下) Android中的MVP模式,带实...

  • 一套完整的Android通用框架

    转自吴小龙同学的博客 MVP模式 MVP简介 Android MVP Sample,MVP+Retrofit+Rx...

  • MVP简单尝试

    MVP模式解析 标签: Android 架构 MVP MVP模式的核心思想 MVP将Activity中的U...

  • MVP基础架构

    MVP 是什么 基础架构 登录例子 MVP的优缺点 一、MVP 是什么 MVP全名是 Model - View -...

  • android 安卓 mvp mvvm - mvp

    android 安卓 mvp mvvm - mvpandroid 安卓 mvp mvvm - mvvm MVP M...

  • MVP架构

    目录 1)MVP简介2)MVP实例 1)MVP简介 MVP模式将Activity中的业务逻辑全部剥离出来,Acti...

  • MVC和MVP

    Android mvp 架构的自述 如何更高效的使用MVP以及官方MVP架构解析 老的MVC架构 新的MVP架构 ...

  • MVP系列-Android平台-第1讲-初探MVP

    MVP系列-Android平台-第1讲-初探MVP 内容一:什么是MVP?什么是MVC? 第一点:什么是MVP? ...

  • MVP框架学习

    一、MVP介绍 二、为什么使用MVP模式 三、MVP与MVC的异同 四、使用MVP实现Android的登录的Dem...

网友评论

      本文标题:MVP

      本文链接:https://www.haomeiwen.com/subject/puzslctx.html