MVP模式

为什么用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的呢?


06120120-8858-40bc-a09d-54cf7afaf542.png

图中显示的三者如何进行通信,其中callback和view都是声明的接口,实质上就是通过接口回调来实现三者之间的交互。
既然分析过了MVP的各个模块以及模块之间的联系,那么我们就通过一个简单的例子来实践一下。

MVP的简单实现

我们用MVP实现下面的效果


cf9eb44d-9540-4d66-9ba7-ef24fbf6b172.png

通过按钮进行网络请求,然后根据网络请求结果去更新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完成,包结构如下


bd1fe28e-4069-4552-8a88-e0809fa18ca0.png

但是如果要运用到项目中还是有很多问题的,比如:
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模式的理解,当然肯定会有不足和理解不到位的地方,欢迎各位大佬交流指正。

你可能感兴趣的:(MVP模式)