本文是对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;
}
网友评论