前言
在淡到Android开发的框架模式时,不外乎MVC、MVVM和MVP三种。而今天,题主想介绍一种新的框架模式——MVPO。这是题主在MVP的基础上扩展出来的一种模式,新的模式当然是为了解决新的问题,而本人相信,在Android开发中困扰着题主的一些问题,同样也困扰着大家。
MVP存在的缺陷
-
View和Presenter对应关系引发的问题。
我们往往一个View(Activity或Fragment)对应一个Presnter,然后Presenter里面封装了若干个网络请求和结果处理。那么问题来了,某个网络请求可能会在不同的Presenter间重复。比如有个接口”获取短信验证码“,我们在RegisterPresenter(注册)和ResetPwdPresenter(重置密码)中都存在一模一样的代码,但这两个Presenter又无法整合成一个,因为它们分别对应不同的功能页面,这两个页面除了获取验证码,其它操作都可能是不同的,强行整合成一个Presenter显然也不合理。
由此,我们遇到了第一个因扰我们的问题:多个Presenter间可能存在重复的代码,却无法优化。 -
MVP无法满足越来越复杂的App开发。
在开发单机App(没有网络请求的App)时代,我们觉得MVC很好用。但现在很少有单机App,很多App靠非常多且复杂的网络请求来支撑视图的展示,于是我们用MVP,通过Presenter把网络请求分离出来,同时处理一些存取数据等辅助操作,目的就是为了让View层更轻更薄。然后到了现在,当App变得更加复杂时,比如题主最近开发的App,一个交互中往往穿插有网络请求和蓝牙操作,于是,为了处理这些复杂的操作,我们只得在Presenter中写入更多的逻辑,P层开始多了很多网络请求以外的重要逻辑。
由此,我们遇到了第二个困扰我们的问题:当开发中夹杂着网络请求及其它同等重要的操作时,P层又会变得臃肿起来。
解决之道
上面提到了两个问题,那么解决这两个问题,就是题主写这两篇文章的目的。在第一篇文章中,我不会直接讲MVPO,而是先针对第一个问题,通过改进MVP来提出解决方案。而改进后的MVP,本身又会被用到后面的MVPO模式中,因此如果有看官比较急,不妨跳过这一章,等待下个一篇章。
好了,我们先看看如何解决第一个问题:多个Presenter间存在重复的代码时,该如何优化?
更合理的View和Presenter对应方式
对此,题主的解决办法是:一个Presenter不再对应一个View,而是对应一个功能模块。比如有三个页面:登录、注册、重置密码。按传统的MVP会有三个Presenter(LoginPresenter、RegisterPresenter、ResetPwdPresenter),现在通通都用同一个Presenter(AccountPresenter)取而代之。
可能有些看官要打脸了,那你这个AccountPresenter岂不是包含了三个页面的所有网络请求,虽然的确不会再有冗余和重复的代码,但一来,这个AccountPresenter也太不专一且臃肿了;二来,这个Presenter还怎么跟专属的某个Activity对应?
废话不多说,还是直接上代码吧。
AccountPresenter
/**
* 登录
*/
public static void login(final String mobile, String pwd, BaseObserver<LoginResult> observer) {
observer.addParam("mobile", mobile)
.addParam("password", pwd)
.post(AppConfig.UrlConfig.LOGIN);
}
/**
* 发送短信验证码
*/
public static void snedSmsCode(String mobile, String sign, String imageCode, BaseObserver observer) {
observer.addParam("mobile", mobile)
.addParam("image_code", imageCode)
.post(AppConfig.UrlConfig.FETCH_SMS_CODE);
}
/**
* 注册新用户
*/
public static void register(String mobile, String smsCode, String pwd, BaseObserver observer) {
observer.addParam("mobile", mobile)
.addParam("sms_code", smsCode)
.addParam("password", pwd)
.post(AppConfig.UrlConfig.REGISTER);
}
/**
* 重置密码
*/
public static void resetPwd(String mobile, String smsCode, String pwd, BaseObserver observer) {
observer.addParam("mobile", mobile)
.addParam("sms_code", smsCode)
.addParam("password", pwd)
.post(AppConfig.UrlConfig.FORGET_PASSWORD);
}
是不是有点反常识,我们居然把登录、注册、重置密码三个页面的所有网络请求都整合进同一个Presenter里,也就是说,我们三个Activity都可以用同一个Presenter,而且还能一一对应,不信接着看代码
LoginActivity
private void doLogin() {
AccountPresenter.login(mMobile, mPwd, new BaseObserver<LoginResult>(this) {
@Override
public void onSuccess(LoginResult loginResult) {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
}
@Override
public void onError(String code, String msg) {
super.onError(code, msg);
}
});
}
RegisterActivity
private void doRegister() {
AccountPresenter.register(mPhone, mSMSCode, mPwd, new BaseObserver(this) {
@Override
public void onSuccess(Object o) {
ToastUtil.show("注册成功");
}
@Override
public void onError(String code, String msg) {
super.onError(code, msg);
}
});
}
ResetPwdActivity
private void doResertPwd() {
AccountPresenter.resetPwd(mPhone, mSMSCode, mPwd, new BaseObserver(this) {
@Override
public void onSuccess(Object o) {
ToastUtil.show("重置密码成功");
}
@Override
public void onError(String code, String msg) {
super.onError(code, msg);
}
});
}
讲解
可能有些看官已经发现了,Presenter里面的方法都是静态的,每个Activity按需调用其中某个或若干个方法即可。这种情况下,P层有点类似于工具类,不再和特定的View绑死,而是可以服务于多个视图页面。但问题来了,我们习惯让P层帮我们处理好Loading动画,比如网络请求开始时自动开始转菊花,网络请求结束后让Loading消失。最重要的是,我们希望Activity结束时,能自动取消未完成的网络请求。上面提到的这些,题主都是有实现的,不过换了种方式,放到了BaseObserver里处理。
没错,BaseObserver才是和View层绑定的重要元素。大家可以看到,每次new BaseObserver,构造函数中都会传入this,这个this其实是一个接口(ILoadingView),而我们的Activity继承的BaseActivity本身实现了这个接口,从而建立绑定关系。不妨来看一下这几者的代码。
ILadingView
public interface ILoadingView {
/**
* 展示Loading动画
*/
void showLoadingDialog(String msg);
/**
* 让Loading动画消失
*/
void hideLoadingDialog();
/**
* 保存当前页面所有网络请求,在页面结束时以便取消网络请求
*/
void addNetRequest(Disposable d);
}
BaseActivity
public abstract class BaseActivity extends AppCompatActivity implements ILoadingView {
protected Dialog mLoadingDialog;
protected List<Disposable> mRequestList;
@Override
public void showLoadingDialog(String msg) {
mLoadingDialog.show(msg);
}
@Override
public void hideLoadingDialog() {
mLoadingDialog.dismiss();
}
@Override
public void addNetRequest(Disposable d) {
if (null == mRequestList) {
mRequestList = new ArrayList<>();
}
mRequestList.add(d);
}
@Override
protected void onDestroy() {
super.onDestroy();
RetrofitFactory.cancel(mRequestList);
}
}
BaseObserver
public abstract class BaseObserver<T> implements Observer<BaseResponse> {
private ILoadingView mLoadingView;
public BaseObserver(ILoadingView loadingView) {
this.mLoadingView = loadingView;
}
@Override
public void onSubscribe(Disposable d) {
if (null != mLoadingView) {
mLoadingView.addNetRequest(d);
}
onStart(d);
}
public void onStart(Disposable d) {
mLoadingView.showLoadingDialog("加载中");
}
public void onError(String code, String msg) {
ToastUtil.show(msg);
}
public void onFinish() {
mLoadingView.hideLoadingDialog();
}
}
最后总结一下吧
- 至此基本已经解释清楚了,我们的Activity和Fragment实现了ILadingView这个接口,并且实现了里面的方法,比如showLoading会转菊花,addNetRequest会把网络请求存下来以便页面退出时结束掉。在new BaseObserver时再把实现的ILoadingView传入其中,从而建立绑定关系。
- 由于P层绑定视图的任务已经转移到BaseObserver中,因此Presenter可以直接做成静态工具类,从而可以服务于多个页面,而不会相互干扰。
未完待续
回到开篇提的第一个问题:多个Presenter间存在重复的代码时,该如何优化?通过以上改进后的MVP框架,我们很好解决了这个问题,让Presenter间不再有重复冗余的代码。但第二个问题:如果App项目在网络请求中夹杂了蓝牙等非常多的重度操作,该如何优化,这问题还是没解决。所以,如果你也被第二个问题所困扰,不妨期待接下来的第二个篇章。
网友评论