Android框架现在常见的有MVC模式、MVP模式、MVVM模式。我们首先先明确一个概念:模式是指组织代码的结构方式,模式并不能提高代码的执行效率。模式是为了后续功能的扩展方便和代码的结构清晰而使用的。
在上一篇文章中,由刚开始的把代码都写在Activity里演化出Android的MVC模式,但是在最后,我们可能还是感觉Activity中的代码有些繁杂,这是因为V层和C层的角色都由Activity来承担,要想更进一步的话,就需要我们再进一步了解MVP模式。
MVP模式即将软件分为三个基本部分:模型(Model)、视图(View)和主持人(Presenter)(我暂且翻译成主持人)。
模型(Model)- 用来产生数据(访问网络、数据库等)
视图(View)- 视图层相关(界面布局控件等相关的代码:Activity、Fragment)
主持人(Presenter)- 连接M和V的(业务逻辑相关的代码)
在MVC模式中,Activity同时承担着V层和C层的角色,即Activity中除了有布局控件等相关的代码还有业务逻辑相关的代码,此时我们把Activity中的业务逻辑相关的代码全部抽离出去就变成了MVP模式。在MVP模式中,Activity中只包含界面布局控件等相关的代码,不再包含业务逻辑相关的代码,Activity只承担View层的角色。并且Model层和View层不直接交互,而是由Presenter作为中间人,Presenter会持有 Model 和 View 的对象。
一、抽离业务逻辑相关代码,实现MVP模式
首先在项目中创建一个presenter包,然后创建LoginPresenter.java类,抽取Activity中的业务逻辑相关的代码就形成了登录的Presenter层:
//登录模块的业务逻辑
public class LoginPresenter {
private static final String TAG = "LoginPresenter";
private LoginActivity loginActivity;
public LoginPresenter(LoginActivity loginActivity) {
this.loginActivity = loginActivity;
}
public void login(String phoneNumber, String password) {
//本地对输入情况做校验
boolean validateOk = validateInput(phoneNumber, password);
if (validateOk) {
loginActivity.showProgressBar();
String md5Password = MD5Utils.getMd5(password);
LoginModel loginModel = new LoginModel();
loginModel.gotoLogin(phoneNumber, md5Password, new LoginModel.OnNetResponseListener() {
@Override
public void onNetResposeError(String msg) {
loginActivity.hideProgressBar();
loginActivity.showToast(msg);
}
@Override
public void onNetReponseSuccess(LoginData loginData) {
loginActivity.hideProgressBar();
switch (loginData.status) {
case 200: //用户名未注册
case 201: //密码有误
case 203: //登录失败
loginActivity.showToast(loginData.message);
Log.i(TAG, "onResponse: = " + loginData.message);
break;
case 202: //登录成功
loginActivity.showToast(loginData.message);
Log.i(TAG, "onResponse: = " + loginData.message);
//本地保存必要的用户信息
//......
loginActivity.jumpSuccessActivity(loginData);
break;
default:
loginActivity.showToast("登录出现未知异常");
break;
}
}
});
}
}
private boolean validateInput(String phoneNumber, String password) {
if (TextUtils.isEmpty(phoneNumber)) {
loginActivity.showToast("手机号不能为空");
return false;
}
if (TextUtils.isEmpty(password)) {
loginActivity.showToast("密码不能为空");
return false;
}
if (!phoneNumber.matches(Constants.STR_PHONE_REGEX2)) { //匹配正则表达式
loginActivity.showToast("请输入正确的手机号");
return false;
}
return true;
}
}
此时,Activity就只有界面布局控件等相关的代码,即View层变成:
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "LoginActivity";
private EditText etPhoneNumber;
private EditText etPassword;
private Button btnLogin;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
etPhoneNumber = (EditText) findViewById(R.id.et_phone_number);
etPassword = (EditText) findViewById(R.id.et_password);
btnLogin = (Button) findViewById(R.id.btn_login);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
btnLogin.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login: //登录
String phoneNumber = etPhoneNumber.getText().toString().trim();
String password = etPassword.getText().toString().trim();
//login(phoneNumber, password);
LoginPresenter loginPresenter = new LoginPresenter(this);
loginPresenter.login(phoneNumber, password);
break;
default:
break;
}
}
public void showProgressBar(){
progressBar.setVisibility(View.VISIBLE);
}
public void hideProgressBar(){
progressBar.setVisibility(View.GONE);
}
public void showToast(String toast){
Toast.makeText(this, toast, Toast.LENGTH_SHORT).show();
}
public void jumpSuccessActivity(LoginData loginData){
Intent intent = new Intent(LoginActivity.this, LoginSuccessActivity.class);
startActivity(intent);
//登录页面直接消失
finish();
}
}
Model层还和原来的一样,不变:
//Model层只用来产生数据的
public class LoginModel {
private static final String TAG = "LoginModel";
public void gotoLogin(String phoneNumber, String md5Password, final OnNetResponseListener listener) {
OkHttpUtils
.post()
.url(Constants.URL_LOGIN)
.addParams("phoneNumber", phoneNumber)
.addParams("password", md5Password)
.build()
.execute(new StringCallback() {
@Override
public void onError(okhttp3.Call call, Exception e, int id) {
Log.i(TAG, "onError: ---网络访问出现异常---" + e.getMessage());
e.printStackTrace();
listener.onNetResposeError("网络访问出现异常");
}
@Override
public void onResponse(String response, int id) {
Log.i(TAG, "onResponse: 登录成功 response = " + response + " ---");
Gson gson = new Gson();
LoginData loginData = gson.fromJson(response, LoginData.class);
listener.onNetReponseSuccess(loginData);
}
});
}
public interface OnNetResponseListener {
void onNetResposeError(String msg);
void onNetReponseSuccess(LoginData loginData);
}
}
此时LoginActivity已经分离成独立的M、V、P三层:Model层只用来产生数据,View层即Activity只包含界面布局控件等相关的代码、Presenter层负责相关业务逻辑的处理。并且Model层和View层不直接交互。
二、Presenter层适用性的扩展
上面的例子我们使用LoginActivity作为View层,如果登录页面变成Fragment比如LoginFragment,那么不仅View层的代码需要更改,Presenter层的代码也需要修改,因为上面的LoginPresenter持的View层对象是LoginActivity,这样显然是不合适的,因为Presenter层负责业务逻辑的处理,并不用关心View层到底是Activity还是Fragment,View层的变化也不应该影响Presenter层的代码,Presenter层的代码应该是相对独立的。
此时就需要我们为View层定义一种 规则(规范)即接口,Presenter层持的View层对象是一个接口,而Presenter层并不用关注这个接口的具体实现是谁。我们新建一个view包,并创建一个ILoginView接口,用来规范登录页面相关的UI操作:
public interface ILoginView {
public void showProgressBar();
public void hideProgressBar();
public void showToast(String toast);
public void jumpSuccessActivity(LoginData loginData);
}
然后将上面LoginPresenter持的View层引用改为ILoginView接口:
//登录模块的业务逻辑
public class LoginPresenter {
private static final String TAG = "LoginPresenter";
private ILoginView loginView;
public LoginPresenter(ILoginView loginView) {
this.loginView = loginView;
}
public void login(String phoneNumber, String password) {
//本地对输入情况做校验
boolean validateOk = validateInput(phoneNumber, password);
if (validateOk) {
loginView.showProgressBar();
String md5Password = MD5Utils.getMd5(password);
LoginModel loginModel = new LoginModel();
loginModel.gotoLogin(phoneNumber, md5Password, new LoginModel.OnNetResponseListener() {
@Override
public void onNetResposeError(String msg) {
loginView.hideProgressBar();
loginView.showToast(msg);
}
@Override
public void onNetReponseSuccess(LoginData loginData) {
loginView.hideProgressBar();
switch (loginData.status) {
case 200: //用户名未注册
case 201: //密码有误
case 203: //登录失败
loginView.showToast(loginData.message);
Log.i(TAG, "onResponse: = " + loginData.message);
break;
case 202: //登录成功
loginView.showToast(loginData.message);
Log.i(TAG, "onResponse: = " + loginData.message);
//本地保存必要的用户信息
//......
loginView.jumpSuccessActivity(loginData);
break;
default:
loginView.showToast("登录出现未知异常");
break;
}
}
});
}
}
private boolean validateInput(String phoneNumber, String password) {
if (TextUtils.isEmpty(phoneNumber)) {
loginView.showToast("手机号不能为空");
return false;
}
if (TextUtils.isEmpty(password)) {
loginView.showToast("密码不能为空");
return false;
}
if (!phoneNumber.matches(Constants.STR_PHONE_REGEX2)) { //匹配正则表达式
loginView.showToast("请输入正确的手机号");
return false;
}
return true;
}
}
此时LoginPresenter就不用关心View层的具体实现是Activity还是Fragment,只需要它们实现ILoginView这个接口并重写抽象方法即可。这样presenter层的适用性就大大增加了。甚至依据这种思路,我们也可以为Presenter创建一个接口,让LoginPresenter实现这个接口。
最终呈现的包结构应该是这样的:
以上就是以登录为例实现的MVP模式。