为什么用MVP模式
这个好处其实主要是和传统的MVC模式对比的,那我们认知中Android开中的MVC模式到底是什么呢?
MVC
MVC架构在Android中,M代表的是Model,通常我们建的实体类属于这个范畴;V则是View,通常指我们创建的xml布局文件;C是Controller控制层,主要是Activity和Fragment。
不管是MVC还是MVP,都有它的优劣势,必须根据项目具体分析使用哪种模式。
MVP好处
随着项目的增大,MVC模式的控制层即Activity或者Fragment处理了太多逻辑,导致一个文件多达上千行代码,不管是维护还是查看逻辑都带来了相当大的不便。
MVP和MVC一样分为三层,Model层主要是做一些网络请求的工作,View层主要是指Activity和Fragment,和MVC中的activity和Fragment最大的区别是在MVP模式中,View层只处理和UI相关的内容,逻辑处理则完全放在了Presenter层中,Presenter里面全是java代码,不涉及Android的API这样层次分明,既解耦又方便代码管理。
三层之间的关系是View——>Presenter——>Model。
Model是如何将请求到的数据反馈给Presenter,而Presenter如何操控View去更新UI的呢?
图中显示的三者如何进行通信,其中callback和view都是声明的接口,实质上就是通过接口回调来实现三者之间的交互。
既然分析过了MVP的各个模块以及模块之间的联系,那么我们就通过一个简单的例子来实践一下。
MVP的简单实现
我们用MVP实现下面的效果
通过按钮进行网络请求,然后根据网络请求结果去更新UI
Callback接口
callback接口是Model层给Presenter反馈的信息载体,所以需要定义网络请求的各种状态,同时返回请求结果
public interface Callback {
/**
* 网络请求成功
* @param data 请求到的数据
*/
void onSuccess(Object data);
/**
* 根据接口API请求,虽然网络请求成功,
* 但是根据API定义的code并不是正常值
* @param data
*/
void onFail(Object data);
/**
*网络请求错误
*/
void onError();
/**
*不管是请求成功还是失败都会执行的操作
* 比如加载loading消失
*/
void onComplete();
}
Model类
model类中定义了具体的网络请求,我们这里用伪代码去模拟网络请求
public class MvpModel {
public static void getData(String params, final Callback callback){
NetConnectManager.getInstance
.getData(params)
.next(new NetListener(Object obj){
@Override
public void onSuccess(Object obj){
callback.onSuccess(obj);
callback.onComplete();
}
@Override
public void onFail(Object obj){
callback.onFail(obj);
callback.onComplete();
}
@Override
public void onError(){
callback.onError();
callback.onComplete();
}
});
}
}
View接口
view接口是Activity和Presenter的中间层,它的作用是根据具体的业务逻辑需求,为Presenter提供Activity中具体调用更新ui的方法(提供的接口方法同样是模拟的一些常用ui更新方法,这个接口中方法还是要根据具体业务中处理ui去定义具体方法)
public interface MvpView {
/**
* 显示正在加载进度框
*/
void showLoading();
/**
* 隐藏正在加载进度框
*/
void hideLoading();
/**
* 当数据请求成功后,调用此接口显示数据
* @param data 数据源
*/
void showData(Object data);
/**
* 当数据请求失败后,调用此接口提示
* @param msg 数据源
*/
void showFailure(Object msg);
/**
* 当数据请求异常,调用此接口提示
*/
void showError();
}
xml布局文件
这个没什么好说的,根据需求去写
Activity
既然view接口中定义的是更新ui的方法,所以这个接口需要在acitivity或者fragment中去实现
public class MainActivity extends AppCompatActivity implements MvpView{
private MvpPresenter mPresenter;
private TextView mUpdateTv;
private Button mBtn;
private ProgressDialog mProDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUpdateTv = findViewById(R.id.tv);
mBtn = findViewById(R.id.btn);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.getData("params");
}
});
mProDialog = new ProgressDialog(this);
mProDialog.setCancelable(false);
mProDialog.setMessage("loading");
//初始化Presenter,通过构造方法将Presenter和Activity联系起来
mPresenter = new MvpPresenter(this);
}
@Override
public void showLoading() {
mProDialog.show();
}
@Override
public void hideLoading() {
mProDialog.dismiss();
}
@Override
public void showData(Object data) {
mUpdateTv.setText("success");
}
@Override
public void showFailure(Object msg) {
mUpdateTv.setText("fail");
}
@Override
public void showError() {
mUpdateTv.setText("error");
}
}
Presenter类
Presenter是一个纯java类,不包含任何Android Api,它的作用就是根据获得的网络请求去更新UI,为了让它实现这个功能,我们给它的构造方法传一个view接口,同时实现callback接口。
public class MvpPresenter implements Callback{
private MvpView mvpView;
public MvpPresenter(MvpView mvpView) {
this.mvpView = mvpView;
}
/**
* 调用MvpModel的方法,进行相应的网络请求
* @param params
*/
public void getData(String params){
mvpView.showLoading();
MvpModel.getData(params,this);
}
@Override
public void onSuccess(Object data) {
mvpView.showData(data);
}
@Override
public void onFail(Object data) {
mvpView.showFailure(data);
}
@Override
public void onError() {
mvpView.showError();
}
@Override
public void onComplete() {
mvpView.hideLoading();
}
}
至此,一个简易的依据MVP模式构建的Demo完成,包结构如下
但是如果要运用到项目中还是有很多问题的,比如:
1.如果每建立一个activity或者fragment都像上述demo中去创建相应的类和方法,会造成大量的代码冗余。
2.架构存在安全漏洞,如果当前activity异常销毁,那么通过Presenter里面的view去调用activity方法就会发生空指针异常。
接下来我们处理以上问题:
Presenter通过接口回调可能引发的空指针异常
这个问题发生的原因无非是我们在调用的过程中无法获取activity的状态,解决办法显而易见是让view接口和activity生命周期进行绑定,然后在presenter进行接口调用的,时候判断view是否存在即可。我们对presenter进行如下改进:
public class MvpPresenter implements Callback{
private MvpView mvpView;
/**
* 不再通过构造方法传入view接口
*/
public MvpPresenter() {
}
/**
* 在activity的onCreate方法中绑定
* @param mvpView
*/
public void attachView(MvpView mvpView){
this.mvpView = mvpView;
}
/**
* 在activity的onDestory方法中解绑
*/
public void detachView(){
mvpView = null;
}
/**
* 通过view接口调用更新ui方法前,先判断是否解绑
* @return
*/
public boolean isViewAttach(){
return mvpView != null;
}
/**
* 调用MvpModel的方法,进行相应的网络请求
* @param params
*/
public void getData(String params){
if(isViewAttach()){
mvpView.showLoading();
}
MvpModel.getData(params,this);
}
@Override
public void onSuccess(Object data) {
if (isViewAttach())
mvpView.showData(data);
}
@Override
public void onFail(Object data) {
if(isViewAttach())
mvpView.showFailure(data);
}
@Override
public void onError() {
if (isViewAttach())
mvpView.showError();
}
@Override
public void onComplete() {
if(isViewAttach())
mvpView.hideLoading();
}
}
对于attachView方法和detachView方法分别在对应的activity的onCreate和onDestory中调用,代码就不贴出了。
构建各个base父类
即使解决了因为activity意外销毁导致的空指针异常,大家也不会对刚刚demo中的mvp模式感到一丝兴趣,因为冗余代码实在太多,接下来我们为每一个模块设计一个顶层父类来减少冗余代码。
BaseCallback
callback作用是将,model层网络请求的结果传递给presenter层使用,现实开发中,后台服务器返回给我们的数据通常会做一层封装,比如
{
code:“”
data:{}
message:“”
}
每个接口返回的结构都相同,只不过是data里面的数据不一样,我们就可以创建一个BaseResponse的类,根据不同接口创建不同的data实体类去继承BaseResponse。然后在Callback中用泛型来获得具体请求结果。
所以顶层的Callback我们可以这样,
public interface BaseCallback {
/**
* 网络请求成功
* @param data 请求到的数据
*/
void onSuccess(T data);
/**
* 根据接口API请求,虽然网络请求成功,
* 但是根据API定义的code并不是正常值
* @param data
*/
void onFail(T data);
/**
*网络请求错误
*/
void onError();
/**
*不管是请求成功还是失败都会执行的操作
* 比如加载loading消失
*/
void onComplete();
}
BaseView
View接口定义的是activity中ui逻辑,在BaseView中可以定义一些通用的方法,比如获取context,弹出Toast,loading的加载等
public interface BaseView {
/**
* 显示正在加载进度框
*/
void showLoading();
/**
* 隐藏正在加载进度框
*/
void hideLoading();
/**
* @param message
*/
void showToast(String message);
Context getContext();
}
BasePresenter
Presenter的封装主要是针对View接口的绑定,根据需求不同,回调不同的View接口的方法,所以这里我们也用泛型来处理:
public class BasePresenter {
private V mvpView;
/**
* 不再通过构造方法传入view接口
*/
public BasePresenter() {
}
/**
* 在activity的onCreate方法中绑定
* @param mvpView
*/
public void attachView(V mvpView){
this.mvpView = mvpView;
}
/**
* 在activity的onDestory方法中解绑
*/
public void detachView(){
mvpView = null;
}
/**
* 通过view接口调用更新ui方法前,先判断是否解绑
* @return
*/
public boolean isViewAttach(){
return mvpView != null;
}
/**
* @return 返回绑定的view接口
*/
public V getView(){
return mvpView;
}
}
BaseMvpActivity
在BaseMvpActivity中主要通过View接口和Presenter联系起来,对于继承BaseMvpActivity的activity需要传入对应的presenter和view接口:
public abstract class BaseMvpActivity> extends AppCompatActivity implements BaseView {
private P presenter;
private V view;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (this.presenter == null) {
//创建P层
this.presenter = createPresenter();
}
if (this.view == null) {
//创建V层
this.view = createView();
}
//绑定
if (this.presenter == null) {
throw new NullPointerException("Presenter不能为空");
}
if (this.view == null) {
throw new NullPointerException("View不能为空");
}
this.presenter.attachView(this.view);
}
public P getPresenter(){
return presenter;
}
//并不知道具体P是哪一个实现类,此方法由子类自己调用
public abstract P createPresenter();
//并不知道具体V是哪一个实现类,此方法由子类自己调用
public abstract V createView();
...
...
...
@Override
protected void onDestroy() {
super.onDestroy();
if (this.presenter != null) {
this.presenter.detachView();
}
}
}
解决了架构中存在的安全漏洞,以及对各个模块进行简单的封装,至此,一个基本的MVP架构构建完成。
但是。。。
(看到这俩字心里是不是又一juling。。。)
代码是死的,在各个场景中应该灵活运用才能最大化的提升工作效率。
例如:
1.有的模块并没有网络请求或者很简单的网络逻辑处理,完全没有必要遵从MVP模式去创建这么多类,用传统的MVC效率会更高。
2.我在实际运用中并没有每一个activity都去创建一个对应的view接口,而是在BaseView中,增加了两个方法
void success(T data,int which);
void fail(T data,int which);
其实activity的ui逻辑是伴随着网络请求的结果去实现的,这样做每个网络请求结果回来通过switch去处理ui逻辑,一是省的创建view接口,通过which判断即可,也省去了一堆方法的命名,代码看起来更清晰。
这些只是我个人对于MVP模式的理解,当然肯定会有不足和理解不到位的地方,欢迎各位大佬交流指正。