1. 概述
1.1 MVC中的三个角色
M:model,数据的存取功能和实体模型
V:view,对应布局文件
C:controller,对应Activity(因为view对应布局文件,因此能做的事情很少,会在Activity中进行事件处理、数据绑定等,使得Activity即像view又像controller,臃肿不堪)
1.2 MVP
出现原因
为了让数据和视图分离
MVP中的三个角色
M:model,封装数据存取操作(包括操作数据库或通过网络获取数据)和实体模型;
V:view,对应Activity或Fragment或某个View控件。view中持有presenter的引用;
P:presenter,沟通model和view的桥梁。presenter中持有view和model的引用;
MVP结构图
图中说明:MVP中presenter层是view和model之间的桥梁,实现了数据和视图的分离
1.3 MVC与MVP
- MVC是允许Model和View进行交互的,而MVP中Model和View之间的交互由Presenter完成,且Presenter和View之间的交互通过接口完成。
- 二者间最大的不同:Activity职责的变化,由原来的C(控制层)变成了V(视图层),在MVP中控制层的角色由P来担当。这种架构解决了Activity过度耦合控制层和视图层的问题,实现了数据和视图分离。
2. 使用
演示Login功能。
数据传递路径
数据请求流程:当view层某个界面需要展示数据时,首先会调用presenter层的引用——>presenter层会调用model层请求数据——>当model层数据加载成功后会调用presenter层的回调方法通知presenter层数据加载情况——>presenter层再调用view层的接口将加载后的数据展示给用户。
面向接口编程
预期效果
当用户输入账号为aaa,密码为123时跳转到主页面,其余情况给出错误提示。
开始写代码
- 从View开始写起,当View中需要操作数据时,调用presenter的相关方法。
点击登录按钮时需要进行数据校验,通过调用presenter中的方法进行数据操作
/**
* <pre>
* author : 杨丽金
* time : 2017/12/26
* desc : LoginView接口中说明了Presenter要求View提供的各个
* version: 1.0
* </pre>
*/
public class LoginActivity extends AppCompatActivity implements LoginView {
private EditText mEdtUsername;
private EditText mEdtPsw;
private Button mBtnLogin;
private ProgressBar mPbProgress;
// View(此处是Activity)中持有Presenter的引用
private LoginPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
setListener();
}
private void initView() {
mEdtUsername = (EditText) findViewById(R.id.edt_username);
mEdtPsw = (EditText) findViewById(R.id.edt_psw);
mBtnLogin = (Button) findViewById(R.id.btn_login);
mPbProgress = (ProgressBar) findViewById(R.id.pb_progress);
mPbProgress.setVisibility(View.GONE);
presenter = new LoginPresenterImpl(this);
}
private void setListener() {
mBtnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String username = mEdtUsername.getText().toString();
String psw = mEdtPsw.getText().toString();
User user = new User(username, psw);
// 此处需要操作数据,activity不自己进行数据存取,而是调用presenter中的方法
presenter.validate(user);
}
});
}
}
- presenter再调用model中的相关方法;
/**
* <pre>
* author : 杨丽金
* time : 2017/12/26
* desc : LoginPresenter接口中说明了View中要求Presenter中提供的方法;
* OnLoginFinishedListener接口中说明了Model中要求Presenter中提供的方法
* version: 1.0
* </pre>
*/
public class LoginPresenterImpl implements LoginPresenter,OnLoginFinishedListener {
// presenter中持有view层的引用
private LoginView loginView;
// presenter中持有model层的引用,LoginModel接口中指明了Presenter要求Model中提供的方法
private LoginModel loginModel;
// 在构造函数中对loginview和loginModel进行初始化
public LoginPresenterImpl(LoginView loginView) {
this.loginView = loginView;
this.loginModel=new LoginModelImpl();
}
@Override
public void validate(User user) {
/*presenter是view和model之间的桥梁,不进行什么实质性的操作,
* 只是把要做的操作传递到view或Model中,view或Model才是操作的具体执行者
*/
if (loginView != null) {
// 显示进度条
loginView.showProgress();
}
loginModel.login(user,this);
}
}
- model加载数据完成后调用presenter中的回调方法返回数据加载完成情况
/**
* <pre>
* author : 杨丽金
* time : 2017/12/26
* desc :
* version: 1.0
* </pre>
*/
public class LoginModelImpl implements LoginModel {
@Override
public void login(final User user, final OnLoginFinishedListener listener) {
final String username = user.getName();
final String password = user.getPassword();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (TextUtils.isEmpty(username)) {
listener.onUsernameIsNull();//model层里面回调listener
return;
}
if (TextUtils.isEmpty(password)) {
listener.onPasswordIsNull();
return;
}
if ("aaa".equals(username) && "123".equals(password)) {
listener.onSuccess();
}else{
listener.onError();
}
}
}, 2000);
}
}
- presenter知道数据加载情况(加载成功或失败或其他意外情况)后,调用view的接口指使view完成界面跳转等操作
/**
* <pre>
* author : 杨丽金
* time : 2017/12/26
* desc : LoginPresenter接口中说明了View中要求Presenter中提供的方法;
* OnLoginFinishedListener接口中说明了Model中要求Presenter中提供的方法
* version: 1.0
* </pre>
*/
public class LoginPresenterImpl implements LoginPresenter,OnLoginFinishedListener {
@Override
public void onUsernameIsNull() {
if (loginView != null) {
loginView.setUsernameIsNull();
loginView.hideProgress();
}
}
@Override
public void onPasswordIsNull() {
if (loginView != null) {
loginView.setPswIsNull();
loginView.hideProgress();
}
}
@Override
public void onSuccess() {
if (loginView != null) {
loginView.moveToMainPage();
loginView.hideProgress();
}
}
@Override
public void onError() {
if (loginView != null) {
loginView.setUsernameOrPswError();
loginView.hideProgress();
}
}
}
注意
- mvp的写法导致presenter中持有Activity引用,可能会造成内存泄漏,故退出Activity时要将presenter中持有的Activity的引用置为null
/**
* <pre>
* author : 杨丽金
* time : 2017/12/26
* desc : LoginView接口中说明了Presenter要求View提供的各个
* version: 1.0
* </pre>
*/
public class LoginActivity extends AppCompatActivity implements LoginView {
@Override
protected void onDestroy() {
presenter.onDestroy();
super.onDestroy();
}
}
/**
* <pre>
* author : 杨丽金
* time : 2017/12/26
* desc : LoginPresenter接口中说明了View中要求Presenter中提供的方法;
* OnLoginFinishedListener接口中说明了Model中要求Presenter中提供的方法
* version: 1.0
* </pre>
*/
public class LoginPresenterImpl implements LoginPresenter,OnLoginFinishedListener {
@Override
public void onDestroy() {
loginView=null;
}
}
代码地址
3. 参考文献
一个优秀的Android应用从建项目开始
Android MVP 的简单介绍与使用
LiveCircle
Android MVP架构的自述