说起MVP就不得不提起MVC, 因为MVP的是在MVC的基础上优化而来的:
MVC角色说明:
| 角色 | 职责 |
| :-- | :-- |
| View | 视图界面层,与用户发生交互,接收用户输入的请求转发给Controller处理 |
| Controller | 接收View的请求, 从视图层获取数据,执行业务逻辑,并调用Model层进行数据存取 |
| Model | 执行数据存取的业务逻辑,并根据业务模型通知视图层更新(一般是通过观察者模式) |
MVP角色说明:
| 角色 | 职责 |
| :-- | :-- |
| View | 视图界面层,与用户发生交互,接收用户输入的请求转发给Presenter处理 |
| Presenter | 接收View的请求, 从视图层获取数据,执行业务逻辑,并调用Model层进行数据存取,同时会调用View层的接口将数据更新到视图 |
| Model | 执行数据存取的业务逻辑,并将数据返回给Presenter层 |
可以看到MVP与MVC最大的区别就是MVP解耦了View层和Model层,两者不直接发生交互而是通过P层,而在MVC中Model还是会跟View层发生交互的。
传统的MVC模式更加适合于大型项目的开发,对于小型项目应用MVC反而显得臃肿繁琐,就像我们以前经常在Activity中最喜欢写的代码一样:UI处理、网络请求、数据存取等全部业务都放在Activity中来完成,这就像一个大杂烩,此时的Activity几乎兼顾了MVC中的所有角色,实际上在我看来它此时根本就不具备任何模式可言。而MVP模式的出现将臃肿的部分分解开来,恰好适合于小型的应用程序,比如移动端的应用,并且对单元测试也比较友好,所以大家都在提倡用这个模式进行开发。
按照惯例,还是从一个简单的登录页面来演示MVP的基本使用
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
(几乎我看过的所有关于MVP的文章貌似都是拿登录页面来入门的。。)
布局页面:
布局activity_login.xml代码:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“vertical”
android:gravity=“center”
<EditText
android:id="@+id/edit_user_name"
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:inputType=“text”
android:maxLines=“1”
android:hint=“请输入用户名”
android:textSize=“16sp”
android:textColor="@color/black" />
<EditText
android:id="@+id/edit_user_password"
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:inputType=“textPassword”
android:maxLines=“1”
android:hint=“请输入密码”
android:textSize=“16sp”
android:textColor="@color/black" />
<Button
android:id="@+id/btn_login"
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_marginTop=“20dp”
android:text=“登录”
android:textSize=“16sp”
android:textColor="@color/black"
/>
LoginActivity代码:
public class LoginActivity extends Activity implements View.OnClickListener {
private EditText mUserNameEdit;
private EditText mUserPasswordEdit;
private Button mLoginBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
}
private void initView() {
mUserNameEdit = (EditText) findViewById(R.id.edit_user_name);
mUserPasswordEdit = (EditText) findViewById(R.id.edit_user_password);
mLoginBtn = (Button) findViewById(R.id.btn_login);
mLoginBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login:
//TODO
break;
default:
break;
}
}
}
如果是传统的MVC的话,到这一步基本就结束了,接下来就开始在LoginActivity中搞事情作死了。。
如果使用MVP的话,接下来我们需要建立几个package来存放对应的角色:
其中view包下存放activity、fragment等UI控件,iview包下存放presenter与view进行交互的接口类,我们把LoginActivity放入view/activity包中:
其他包下面的情况:
在MVP中为了解耦Presenter跟View和Model的交互都通过接口进行,所以新建一个跟LoginActivity
交互的接口类ILoginView
:
public interface ILoginView {
/** 获取输入框的登录用户名 */
String getUserName();
/** 获取输入框的用户密码 */
String getUserPassword();
/** 显示Toast提醒 */
void showToast(String msg);
/** 登录成功的UI处理 */
void onLoginSuccess();
/** 登录失败的UI处理 */
void onLoginFail();
/** 显示加载中弹窗 */
void showProgressDialog();
/** 隐藏加载中弹窗 */
void hideProgressDialog();
/** 获取当前UI页面的上下文 */
Context getContext();
}
view
接口类中定义的基本都是一些UI
数据或者显示UI
控件的方法,然让LoginActivity
实现这个接口:
public class LoginActivity extends Activity implements ILoginView, View.OnClickListener {
private EditText mUserNameEdit;
private EditText mUserPasswordEdit;
private Button mLoginBtn;
public ProgressDialog mProgressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
}
private void initView() {
mUserNameEdit = (EditText) findViewById(R.id.edit_user_name);
mUserPasswordEdit = (EditText) findViewById(R.id.edit_user_password);
mLoginBtn = (Button) findViewById(R.id.btn_login);
mLoginBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login:
//TODO
break;
default:
break;
}
}
@Override
public String getUserName() {
return mUserNameEdit.getText().toString();
}
@Override
public String getUserPassword() {
return mUserPasswordEdit.getText().toString();
}
@Override
public void showToast(String msg) {
ToastUtils.showToast(this, msg);
}
@Override
public void onLoginSuccess() {
ToastUtils.showToast(this, “登录成功”);
//跳转首页
gotoHomeActivity();
finish();
}
@Override
public void onLoginFail() {
ToastUtils.showToast(this, “登录失败”);
}
@Override
public void showProgressDialog() {
if (mProgressDialog == null) {
mProgressDialog = DialogUtils.showSpinningProgressDialog(this, “正在登录中…”, false);
} else {
if (!mProgressDialog.isShowing()) {
mProgressDialog.show();
}
}
}
@Override
public void hideProgressDialog() {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
}
@Override
public Context getContext() {
return this;
}
/** 跳转首页 */
private void gotoHomeActivity() {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
}
View
层到此就完事了,接下来实现Presenter
和Model
角色的接口和实现类
Presenter
接口和实现类:
public interface ILoginPresenter {
/** 登录操作逻辑处理 */
void login();
/** 登录成功逻辑处理 */
void onLoginSuccess();
/** 登录失败逻辑处理 */
void onLoginFail(String errMsg);
}
public class LoginPresenter implements ILoginPresenter {
private ILoginView mLoginView;
private ILoginModel mLoginModel;
public LoginPresenter(ILoginView loginView) {
mLoginView = loginView;
mLoginModel = new LoginModelImpl(this);
}
@Override
public void login() {
// 对用户名和密码的校验逻辑,这里只简单判空,实际可以加更多校验
if (TextUtils.isEmpty(mLoginView.getUserName())) {
mLoginView.showToast(“请输入用户名”);
return;
}
if (TextUtils.isEmpty(mLoginView.getPassword())) {
mLoginView.showToast(“请输入密码”);
return;
}
//判断网络是否可用
if (!NetUtils.checkNetState(mLoginView.getContext())) {
mLoginView.showToast(“当前无网络连接,请检查网络”);
return;
}
//显示登录进度弹窗
mLoginView.showProgressDialog();
//调用model层发起登录请求
mLoginModel.sendLoginRequest(mLoginView.getUserName(), mLoginView.getPassword());
}
/** 登陆成功 */
@Override
public void onLoginSuccess() {
//隐藏登录进度弹窗
mLoginView.hideProgressDialog();
//回调View层接口
mLoginView.onLoginSuccess();
}
/** 登陆失败 */
@Override
public void onLoginFail(String errMsg) {
//隐藏登录进度弹窗
mLoginView.hideProgressDialog();
//回调View层接口
mLoginView.onLoginFail(errMsg);
}
}
可以看到在LoginPresenter
的实现类当中分别持有了ILoginView
和ILoginModel
两个接口,LoginPresenter
通过这两个接口分别与LoginActivity和LoginModel进行交互。其中ILoginView
变量是通过构造函数传递进来的,而ILoginModel
则是在构造函数内部创建的。
Model
接口和实现类:
public interface ILoginModel {
/** 发起登录请求 */
void sendLoginRequest(String userName, String password);
}
public class LoginModelImpl implements ILoginModel {
private static final String API_LOGIN = “/mobile/login”;
private ILoginPresenter mPresenter;