目录
1.为什么使用MVP模式
在Android开发中,Activity的首要职责是加载应用的布局和初始化用户界面,接受和处理来自用户的操作请求。但是随着界面和逻辑的复杂度不断的提升,Activity类的职责不断增强而变得庞大臃肿。那么我们就需要通过MVP模式解决混乱、冗余、耦合重的问题。
1.1实例说明
下面是一个用户登录的Demo
public class LoginActivity extends AppCompatActivity {
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
//点击登录
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取账户和密码
final String userName = inputUserName.getText().toString();
final String password = inputPassword.getText().toString();
//判空
boolean isEmptyPassword = userName == null || userName.length() == 0;
//是否符合规范
boolean isUserNameValid = Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(userName).matches();
boolean isPasswordValid = Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(password).matches();
if (isEmptyPassword) {
Toast.makeText(LoginActivity.this, "请输入帐号密码", Toast.LENGTH_SHORT).show();
} else {
if (isUserNameValid && isPasswordValid) {
new Thread(new Runnable() {
@Override
public void run() {
//..登录请求
boolean loginResult = false;
//登录结果
if (loginResult) {
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
}
}
}).start();
} else {
Toast.makeText(LoginActivity.this, "帐号密码格式错误", Toast.LENGTH_SHORT).show();
}
}
}
});
}
}
登录业务实现包含:监听点击事件,获取输入数据,校验数据,判空处理,登录请求,登录结果展示,可明显看到代码混乱,耦合重的问题。那么如果我们将它改为MVP模式,如何拆分呢?首先要理解MVP模式中Model/View/Presenter分别做什么。
2.如何使用MVP模式
MVP代表Model,View和Presenter。
- Model很简单, 进行数据加载。如数据库操作,文件查询, 网络请求。
- View层负责显示数据、用户交互。MVP下Activity和Fragment体现在了这一 层,如加载UI视图、设置监听。
- Presenter层处理各种逻辑的分发,收到View层UI上的反馈的指令后分发处理逻辑交由Model层做具体的业务操作。
2.1MVP实现之拆分
理解了MVP模式的基本实现原则之后,Login功能就可以拆分为:
1、取值, EditText帐号与密码(明确的View层,不涉及逻辑操作)
2、判空与校验 (Presenter但涉及View, 因为使用帐号与密码,通过传参的形式)
3、登录请求 (名副其实的Model, 处理明显在Presenter层)
4、更新UI (View层)
我们按照这样的拆分方式重新写一遍登录请求的代码。
Model层
public class LoginModel {
public LoginModel() {
}
//回调的接口
public interface OnLoginCallback{
void onResponse(boolean success);
}
public void login(String username,String password,final OnLoginCallback onLoginCallback){
//..登录请求
boolean loginResult = false;
//登录结果
onLoginCallback.onResponse(loginResult);
}
}
View层
public class LoginActivity extends AppCompatActivity {
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
inputUserName = this.findViewById(R.id.et_username);
inputPassword = this.findViewById(R.id.et_password);
btnLogin = this.findViewById(R.id.bt_login);
//创建Presenter
presenter = new LoginPresenter(this);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//调用Presenter层,登录逻辑execureLogin()
presenter.execureLogin(getEditorText(inputUserName),getEditorText(inputPassword));
}
});
}
//在View层获取输入数据
private String getEditorText(EditText et) {
return et.getText().toString();
}
//在View层显示结果
public void notifyLoginResult(boolean loginResult) {
if (loginResult) {
showTips("登录成功");
} else {
showTips("登录失败");
}
}
public void showTips(String verifyMsg) {
Toast.makeText(LoginActivity.this, verifyMsg, Toast.LENGTH_SHORT).show();
}
}
Presenter层
public class LoginPresenter {
private LoginActivity activity;
private LoginModel model;
private String verifyMsg;
public LoginPresenter(LoginActivity activity) {
//获取View层
this.activity = activity;
//获取model对象
this.model = new LoginModel();
}
public void execureLogin(String username,String password){
//逻辑类的方法
boolean verifyBefore = verifyBeforeLogin(username,password);
if (verifyBefore) {
//调用login()实现网络请求
model.login(username, password, new LoginModel.OnLoginCallback() {
//实现回调方法
@Override
public void onResponse(boolean success) {
//将结果给到View层
activity.notifyLoginResult(success);
}
});
}else {
activity.showTips(verifyMsg);
}
}
private boolean verifyBeforeLogin(String username, String password) {
boolean isEmpty = isEmpty(username) || isEmpty(password);
boolean isValid = isValid(username) && isValid(password);
if (isEmpty) {
verifyMsg = "请输入帐号或密码";
return false;
}
if (isValid) {
return true;
}
verifyMsg = "帐号或密码错误";
return false;
}
private boolean isValid(String s) {
return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
}
}
虽然根据MVP将页面拆分了三层,但是代码中耦合的情况还是存在:
- Presenter持有View(Activity)和Model对象
- View持有Presenter对象
2.2MVP实现之接口通信
利用接口达到解耦目的,需要View与Presenter分别实现供外部调用的接口。
- View供Presenter调用的方法有
notifyLoginResult
和showTips
; - Presenter供View调用的方法有
executeLogin
。
在接口中提供对外部调用的方法,然后分别在View和Presenter中实现它们,最后把持有对象改为接口,具体实现如下:
定义IPresenter接口类
public interface IPresenter {
//执行登录的接口
void execureLogin(String username,String password);
}
定义IView接口类
public interface IView {
//显示结果的接口
void notifyLoginResult(boolean loginResult);
void showTips(String verifyMsg);
}
//LoginPresenter实现IPresenter接口
public class LoginPresenter implements IPresenter{
//原来的LoginActivity改为了IView
private IView activity;
...
}
----------------------------------------------------------------------------------------
//LoginActivity 实现IView接口
public class LoginActivity extends AppCompatActivity implements IView{
//将原来的LoginPresenter改为IPresenter
IPresenter presenter;
...
}
2.3MVP实现之功能仓库
MVP引入了BaseInterface 与Contract的概念。定义的接口要继承IView与IPresenter, 而且由Contract统一管理。
step1.首先定义公共接口,用于统一规范
public interface IPresenter {
...
//一些公共的接口
}
public interface IView {
...
//一些公共的接口
}
step2.定义的接口要继承IView和IPresenter,由Contract进行统一管理
public interface LoginContract {
interface View extends IView{
//显示结果的接口
void notifyLoginResult(boolean loginResult);
void showTips(String verifyMsg);
}
interface Presenter extends IPresenter{
void login(String name,String password);
}
}
step3.实现IView和IPresenter接口
public class LoginActivity extends AppCompatActivity implements IView {
EditText inputUserName;
EditText inputPassword;
Button btnLogin;
LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
inputUserName = this.findViewById(R.id.et_username);
inputPassword = this.findViewById(R.id.et_password);
btnLogin = this.findViewById(R.id.bt_login);
presenter = new LoginPresenter(this);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.execureLogin(getEditorText(inputUserName),getEditorText(inputPassword));
}
});
}
private String getEditorText(EditText et) {
return et.getText().toString();
}
public void notifyLoginResult(boolean loginResult) {
if (loginResult) {
showTips("登录成功");
} else {
showTips("登录失败");
}
}
public void showTips(String verifyMsg) {
Toast.makeText(LoginActivity.this, verifyMsg, Toast.LENGTH_SHORT).show();
}
}
public class LoginPresenter implements IPresenter {
//原来的LoginActivity改为了IView
private IView activity;
private LoginModel model;
private String verifyMsg;
public LoginPresenter(LoginActivity activity) {
this.activity = activity;
this.model = new LoginModel();
}
public void execureLogin(String username,String password){
boolean verifyBefore = verifyBeforeLogin(username,password);
if (verifyBefore) {
model.login(username, password, new LoginModel.OnLoginCallback() {
@Override
public void onResponse(boolean success) {
activity.notifyLoginResult(success);
}
});
}else {
activity.showTips(verifyMsg);
}
}
private boolean verifyBeforeLogin(String username, String password) {
boolean isEmpty = isEmpty(username) || isEmpty(password);
boolean isValid = isValid(username) && isValid(password);
if (isEmpty) {
verifyMsg = "请输入帐号或密码";
return false;
}
if (isValid) {
return true;
}
verifyMsg = "帐号或密码错误";
return false;
}
private boolean isValid(String s) {
return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
}
}
3.MVP模式的优缺点
MVP的优点:
模型与视图完全分离,降低耦合度,我们可以修改视图而不影响模型;
可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑,提高代码复用。
逻辑放在Presenter中,利于测试驱动开发
MVP缺点:
增加了代码的复杂度,特别是针对小型Android应用的开发,会使程序冗余。
视图和Presenter的交互会过于频繁,一旦视图变更了,presenter也要变更。
参考
android mvp框架步骤,详解Android中的MVP架构分解和实现_weixin_39848347的博客-CSDN博客