上一篇:Android MVP初探
上一篇文章讲了最简单的一个Android MVP,这个只是用来让初学者了解MVP。网络上大部份讲MVP的只到那一步了@_@所以我就写了这篇进阶(还没有看上一篇的请走这里)
上一篇其中存在一些问题:
一. View层ILoginView 中showLoading、hideLoading这两个方法,在其它页也会用到,比如再来一个IRegisterView 注册页面,它同样也要showLoading、hideLoading,对于这些共有的方法抽出来放到一个基础接口里,其它接口继承它,看代码:
public interface MvpView {
/**
* 显示loading对话框
*
* @param msg
*/
void showLoading(String msg);
/**
* 隐藏loading对话框
*/
void hideLoading();
/**
* 显示错误信息
*
* @param errorMsg
*/
void showError(String errorMsg);
}
这里写一个MvpView接口,包含三个方法,这三个方法大部份页面都会有(当然还有其它共有的方法也可以往上加)
改造ILoginView如下:
public interface ILoginView extends MvpView {
/**
* 从UI中获取用户输入的用户名
*
* @return
*/
String getUsername();
/**
* 从UI中获取用户输入的密码
*
* @return
*/
String getPassword();
/**
* 显示结果
*
* @param result
*/
void showResult(String result);
}
ILoginView继承了MvpView,公有的方法在MvpView里,这里只存放特有的方法。
二. Presenter层LoginPresenter存在一个隐藏 的安全隐患:
当其成员loginView对应的页面不存在了,那么在Callback的onSuccess或都onFailure方法中直接调用loginView的hideLoading和showResult方法会报空指针异常。(特别是对于Fragment这种,有时候实例还在但是View已经销毁了,如ViewPager+Fragment这种)
针对这个问题,将Presenter层改造下:
public interface Presenter {
/**
* Presenter与View建立连接
*
* @param mvpView 与此Presenter相对应的View
*/
void attachView(V mvpView);
/**
* Presenter与View连接断开
*/
void detachView();
}
写一个基础接口Presenter它接收MvpView的子类,有两个方法,attachView,detachView分别用于与MvpView建立与断开连接。
然后写一个对Presenter的基础实现类BasePresenter:
public class BasePresenter implements Presenter {
/**
* 当前连接的View
*/
private V mvpView;
/**
* Presenter与View建立连接
*
* @param mvpView 与此Presenter相对应的View
*/
@Override
public void attachView(V mvpView) {
this.mvpView = mvpView;
}
/**
* Presenter与View连接断开
*/
@Override
public void detachView() {
this.mvpView = null;
}
/**
* 是否与View建立连接
*
* @return
*/
public boolean isViewAttached() {
return mvpView != null;
}
/**
* 获取当前连接的View
*
* @return
*/
public V getMvpView() {
return mvpView;
}
/**
* 每次调用业务请求的时候都要先调用方法检查是否与View建立连接,没有则抛出异常
*/
public void checkViewAttached() {
if (!isViewAttached()) {
throw new MvpViewNotAttachedException();
}
}
public static class MvpViewNotAttachedException extends RuntimeException {
public MvpViewNotAttachedException() {
super("请求数据前请先调用 attachView(MvpView) 方法与View建立连接");
}
}
}
BasePresenter是所有Presenter的父类,它实现的Presenter接口,并增加
checkViewAttached方法用于在调用业务前检查当前Presenter是否与MvpView建立连接
isViewAttached方法用于判断是否与MvpView处于连接状态
getMvpView获取当前所连接的MvpView对象
再然后把之前的LoginPresenter改造下:
public class LoginPresenter extends BasePresenter implements ILoginPresenter {
private IUserModel userModel;
public LoginPresenter(IUserModel userModel) {
this.userModel = userModel;
}
/**
* 登录
*/
@Override
public void login() {
checkViewAttached();
getMvpView().showLoading("登录中...");
userModel.login(getMvpView().getUsername(), getMvpView().getPassword(), new Callback() {
@Override
public void onSuccess() {
if (isViewAttached()) {
getMvpView().hideLoading();
getMvpView().showResult("登录成功");
}
}
@Override
public void onFailure(String errorMsg) {
if (isViewAttached()) {
getMvpView().hideLoading();
getMvpView().showResult(errorMsg);
}
}
});
}
}
LoginPresenter继承自BasePresenter并实现ILoginPresenter接口。
相比之前,去掉了成员 loginView,这个通过getMvpView()直接获得。
然后就是login方法中针对空指针隐患的解决:
先调用checkViewAttached()确保View存在才往下走。
然后在Callback回调里调用isViewAttached()判断此时View是否还存在,存在就执行下面的UI操作。
这一步以后,就是LoginActivity的修改:
public class LoginActivity extends AppCompatActivity implements ILoginView, View.OnClickListener {
private EditText username;
private EditText password;
private ProgressDialog progressDialog;
private LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
username = (EditText) findViewById(R.id.username);
password = (EditText) findViewById(R.id.password);
findViewById(R.id.login).setOnClickListener(this);
progressDialog = new ProgressDialog(this);
presenter = new LoginPresenter(new UserModel());
presenter.attachView(this);//这里与View建立连接
}
@Override
protected void onDestroy() {
presenter.detachView();//这里与View断开连接
super.onDestroy();
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.login:
presenter.login();
break;
}
}
/**
* 从UI中获取用户输入的用户名
*
* @return
*/
@Override
public String getUsername() {
return username.getText().toString().trim();
}
/**
* 从UI中获取用户输入的密码
*
* @return
*/
@Override
public String getPassword() {
return password.getText().toString().trim();
}
/**
* 显示结果
*
* @param result
*/
@Override
public void showResult(String result) {
Toast.makeText(LoginActivity.this, result, Toast.LENGTH_SHORT).show();
}
/**
* 显示loading对话框
*
* @param msg
*/
@Override
public void showLoading(String msg) {
progressDialog.setMessage(msg);
if (!progressDialog.isShowing()) {
progressDialog.show();
}
}
/**
* 隐藏loading对话框
*/
@Override
public void hideLoading() {
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
/**
* 显示错误信息
*
* @param errorMsg
*/
@Override
public void showError(String errorMsg) {
Toast.makeText(LoginActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
}
}
这里presenter的实例化只需要传入IUserModel实例,
presenter = new LoginPresenter(new UserModel());
接着关键点来了
在onCreate方法中
presenter.attachView(this);//这里与View建立连接
在onDestroy方法中
presenter.detachView();//这里与View断开连接
至此,针对MVP进阶改造就差不多少了,但是看看LoginActivity:
showLoading,hideLoading,showError这些公共的方法应该像MvpView一样抽出来。
于是产生BaseActivity:
public class BaseActivity extends AppCompatActivity implements MvpView {
protected ProgressDialog progressDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
progressDialog = new ProgressDialog(this);
}
/**
* 显示loading对话框
*
* @param msg
*/
@Override
public void showLoading(String msg) {
progressDialog.setMessage(msg);
if (!progressDialog.isShowing()) {
progressDialog.show();
}
}
/**
* 隐藏loading对话框
*/
@Override
public void hideLoading() {
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
/**
* 显示错误信息
*
* @param errorMsg
*/
@Override
public void showError(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
}
BaseActivity实现了MvpView接口,把这些公共的方法放这里。
然后看看LoginActivity:
public class LoginActivity extends BaseActivity implements ILoginView, View.OnClickListener {
private EditText username;
private EditText password;
private LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
username = (EditText) findViewById(R.id.username);
password = (EditText) findViewById(R.id.password);
findViewById(R.id.login).setOnClickListener(this);
presenter = new LoginPresenter(new UserModel());
presenter.attachView(this);//这里与View建立连接
}
@Override
protected void onDestroy() {
presenter.detachView();//这里与View断开连接
super.onDestroy();
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.login:
presenter.login();
break;
}
}
/**
* 从UI中获取用户输入的用户名
*
* @return
*/
@Override
public String getUsername() {
return username.getText().toString().trim();
}
/**
* 从UI中获取用户输入的密码
*
* @return
*/
@Override
public String getPassword() {
return password.getText().toString().trim();
}
/**
* 显示结果
*
* @param result
*/
@Override
public void showResult(String result) {
Toast.makeText(LoginActivity.this, result, Toast.LENGTH_SHORT).show();
}
}
瞬间清爽了有木有@_@
好了,Android MVP进阶篇就到此结束了,代码看这里
下一篇:Android MVP高级