你的MVP模式写法是否及格

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiaxiazaizai01/article/details/79209288

一般我们做项目都会选择使用MVP进行解耦,关于MVP大家早都耳熟能详了,由于之前接手的项目中使用的MVP模式写法相当随意,同时也存在很多问题,所以下面我们就一步步去实现自己的MVP,顺便分析一下可能会导致出问题的一些写法。当然了,100个人就有一百种MVP写法,你开心就好….

先预留一张gif图(录制gif的工具找不到了,回头补上)

场景

请求干货集中营的妹纸图片,并将数据显示在RecyclerView上,在这里很感谢干货集中营开放的接口,接口地址:http://gank.io/api

1.一般的写法

public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private ImagesAdapter imagesAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        iniViews();
        //加载数据
        getDatas();
    }

    private void iniViews() {
        recyclerView = findViewById(R.id.main_recyclerView);
        imagesAdapter = new ImagesAdapter(this, null);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
        recyclerView.setAdapter(imagesAdapter);
    }

    private void getDatas() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://gank.io/api/data/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        NetApiService service = retrofit.create(NetApiService.class);
        Call<ImagesBean> call = service.getImages("福利", "10", "1");
        call.enqueue(new Callback<ImagesBean>() {
            @Override
            public void onResponse(Call<ImagesBean> call, Response<ImagesBean> response) {
                ImagesBean bean = response.body();
                List<ImagesBean.ResultsBean> imagesLists = bean.getResults();
                if(null != imagesLists && imagesLists.size() > 0){
                    imagesAdapter.setDatas(imagesLists);
                }
            }

            @Override
            public void onFailure(Call<ImagesBean> call, Throwable t) {

            }
        });
    }
}

可以看到我们把网络请求以及更新View控件都写在了一起,如果业务很复杂的话,那么一个MainActivity就会出现成百上千行代码,将会把你看得眼花缭乱。那么我们就开始使用MVP模式来进行优化。

2.MVP初级使用

2.1 View
public interface MvpView<T> {

    void showProgress();
    void hideProgress();
    void loadSuccess(T t);
    void loadFailure(String msg);
}
2.2 Model
public class MvpModel {
    public void request(String category, String count, String pageSize, Callback<ImagesBean> callback){
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://gank.io/api/data/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        NetApiService service = retrofit.create(NetApiService.class);
        Call<ImagesBean> call = service.getImages(category, count, pageSize);
        //发起异步请求
        call.enqueue(callback);
    }
}
2.3 Presenter
public class MvpPresenter {
    private MvpView mvpView;
    private MvpModel mvpModel;

    public MvpPresenter(MvpView mvpView){
        this.mvpView = mvpView;
        mvpModel = new MvpModel();
    }

    public void requstNetDatas(String category, String count, String pageSize){
        //显示加载进度
        mvpView.showProgress();
        mvpModel.request(category, count, pageSize, new Callback<ImagesBean>() {
            @Override
            public void onResponse(Call<ImagesBean> call, Response<ImagesBean> response) {
                mvpView.loadSuccess(response.body());
                mvpView.hideProgress();
            }

            @Override
            public void onFailure(Call<ImagesBean> call, Throwable t) {
                mvpView.loadFailure("数据加载失败!");
                mvpView.hideProgress();
            }
        });
    }
}
2.4 Activity中如何调用
public class MainActivity extends AppCompatActivity implements MvpView<ImagesBean>{
    private ProgressDialog progressDialog;
    private RecyclerView recyclerView;
    private ImagesAdapter imagesAdapter;

    private MvpPresenter mvpPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        iniViews();
        //初始化Presenter
        mvpPresenter = new MvpPresenter(this);
        //加载数据
        getDatas();
    }

    private void getDatas() {
        mvpPresenter.requstNetDatas("福利", "10", "1");
    }

    private void iniViews() {
        recyclerView = findViewById(R.id.main_recyclerView);
        imagesAdapter = new ImagesAdapter(this, null);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
        recyclerView.setAdapter(imagesAdapter);

        progressDialog = new ProgressDialog(this);
        progressDialog.setCancelable(false);
        progressDialog.setMessage("正在拼命加载中...");
    }

    @Override
    public void showProgress() {
        if(!progressDialog.isShowing()){
            progressDialog.show();
        }
    }

    @Override
    public void hideProgress() {
        if(progressDialog.isShowing()){
            progressDialog.dismiss();
        }
    }

    @Override
    public void loadSuccess(ImagesBean imagesBean) {
        imagesAdapter.setDatas(imagesBean.getResults());
    }

    @Override
    public void loadFailure(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
}

这样我们就实现了一个初级版本的MVP模式了,代码感觉清爽多了,Model只负责加载数据,Presenter作为View与Model之间交互的桥梁,Presenter将Model中请求到的结果告诉View,Activity通过实现View接口中的方法去更新数据等等。为什么说这只是一个初级版本呢,试想一下,假设我们在请求网络的过程中由于内存不足,当前的Activity被回收,但是我们的Presenter还持有V层的引用(即对Activity的引用),导致Activity无法被回收,这样就造成了内存泄漏。

3.MVP进一步优化

上面说到了当页面关闭了,但Presenter还持有View的引用,导致内存泄漏。解决办法很简单,我们可以进一步优化我们的Presenter

3.1 Presenter
public class MvpPresenter {
    private MvpView mvpView;
    private MvpModel mvpModel;

    public MvpPresenter(){
        mvpModel = new MvpModel();
    }

    /**
     *  绑定View
     * @param mvpView
     */
    public void attachView(MvpView mvpView){
        this.mvpView = mvpView;
    }

    /**
     *  解绑View
     */
    public void detachView(){
        mvpView = null;
    }

    /**
     *  使用前先检查当前View是否可用
     * @return
     */
    public boolean checkViewAvailable(){
        return mvpView != null;
    }

    public void requstNetDatas(String category, String count, String pageSize){
        if(checkViewAvailable()){
            //显示加载进度
            mvpView.showProgress();
            mvpModel.request(category, count, pageSize, new Callback<ImagesBean>() {
                @Override
                public void onResponse(Call<ImagesBean> call, Response<ImagesBean> response) {
                    if (checkViewAvailable()) {
                        mvpView.loadSuccess(response.body());
                        mvpView.hideProgress();
                    }
                }

                @Override
                public void onFailure(Call<ImagesBean> call, Throwable t) {
                    if (checkViewAvailable()) {
                        mvpView.loadFailure("数据加载失败!");
                        mvpView.hideProgress();
                    }
                }
            });
        }
    }
}

主要修改了Presenter的构造器以及添加了绑定View、解绑View/检查当前View是否还可用等几个方法

3.2 Activity
public class MainActivity extends AppCompatActivity implements MvpView<ImagesBean>{
    private ProgressDialog progressDialog;
    private RecyclerView recyclerView;
    private ImagesAdapter imagesAdapter;

    private MvpPresenter mvpPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        iniViews();
        //初始化Presenter
        mvpPresenter = new MvpPresenter();
        //绑定View
        mvpPresenter.attachView(this);
        //加载数据
        getDatas();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //页面销毁的时候解绑View
        mvpPresenter.detachView();
    }

    private void getDatas() {
        mvpPresenter.requstNetDatas("福利", "10", "1");
    }

    private void iniViews() {
        recyclerView = findViewById(R.id.main_recyclerView);
        imagesAdapter = new ImagesAdapter(this, null);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
        recyclerView.setAdapter(imagesAdapter);

        progressDialog = new ProgressDialog(this);
        progressDialog.setCancelable(false);
        progressDialog.setMessage("正在拼命加载中...");
    }

    @Override
    public void showProgress() {
        if(!progressDialog.isShowing()){
            progressDialog.show();
        }
    }

    @Override
    public void hideProgress() {
        if(progressDialog.isShowing()){
            progressDialog.dismiss();
        }
    }

    @Override
    public void loadSuccess(ImagesBean imagesBean) {
        imagesAdapter.setDatas(imagesBean.getResults());
    }

    @Override
    public void loadFailure(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
}

代码中写了详细的注释,很简单大家都能看懂,这样的话就解决了内存泄漏的问题了,谷歌官方MVP示例demo中是用Fragment来实现View接口,因为Fragment是有生命周期的,所以可以借助Fragment的生命周期,在Activity层关联一个Fragment,在创建Presenter实例的时候将这个Fragment传过去

4.MVP继续优化

上面的方式虽然解决了内存泄漏的问题,但是依旧不够优雅,如果每一个Activity都对应一个Model和Presenter的话,试想一下,如果有几百个Activity的话那么是不是也要创建很多个Model和Presenter,关键是很多地方都是重复的可以通用的。所以,创建基类就显得很有必要了

4.1 View的基类

这里我们只是简单的定义几个通用的接口方法,比如显示加载进度框、关闭加载框等等
BaseMvpView

public interface BaseMvpView {
    void showProgressDialog();
    void hideProgressDialog();
}
4.2 Presenter的基类

BaseMvpPresenter

public abstract class BaseMvpPresenter<V extends BaseMvpView> {
    private V mvpView;
    /**
     *  绑定View
     * @param mvpView
     */
    public void attachView(V mvpView){
        this.mvpView = mvpView;
    }

    /**
     * 解绑View
     */
    public void detachView(){
        mvpView = null;
    }

    /**
     *  获取View
     * @return
     */
    public V getMvpView(){
        return mvpView;
    }
}
4.3 Activity的基类

BaseActivity

public abstract class BaseActivity<V extends BaseMvpView, P extends BaseMvpPresenter<V>> extends AppCompatActivity {

    private P presenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        //实例化Presenter
        presenter = createPresenter();
        //绑定
        if (presenter != null){
            presenter.attachView((V) this);
        }
        //初始化控件
        iniViews();
        //获取数据
        getRequestDatas();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解绑
        if (presenter != null){
            presenter.detachView();
        }
    }

    protected abstract int getLayoutId();
    protected abstract P createPresenter();
    protected abstract void iniViews();
    protected void getRequestDatas(){}

    protected P getPresenter(){
        return presenter;
    }
}

基类有了,那么针对上面获取妹纸图片数据场景,我们就来写具体的实现类。
MvpPresenterImpl

public class MvpPresenterImpl extends BaseMvpPresenter<MvpViewImpl<ImagesBean>> {
    private MvpModel mvpModel;

    public MvpPresenterImpl(){
        this.mvpModel = new MvpModel();
    }

    public void requestNetDatas(String category, String count, String pagesize){
        if (null != getMvpView()) {
            getMvpView().showProgressDialog();
            mvpModel.request(category, count, pagesize, new Callback<ImagesBean>() {
                @Override
                public void onResponse(Call<ImagesBean> call, Response<ImagesBean> response) {
                    if (null != getMvpView()) {
                        getMvpView().loadSuccess(response.body());
                        getMvpView().hideProgressDialog();
                    }
                }

                @Override
                public void onFailure(Call<ImagesBean> call, Throwable t) {
                    if (null != getMvpView()) {
                        getMvpView().loadFailure("请求失败!");
                        getMvpView().hideProgressDialog();
                    }
                }
            });
        }
    }
}

然后就是根据具体的业务场景定义相应的接口了
MvpViewImpl

public interface MvpViewImpl<T> extends BaseMvpView {

    /**
     *  请求成功
     * @param data  接口返回的数据
     */
    void loadSuccess(T data);

    /**
     *  请求失败
     * @param msg 失败原因
     */
    void loadFailure(String msg);
}

最后,我们再来看看Activity类

public class MainActivity extends BaseActivity<MvpViewImpl<ImagesBean>, MvpPresenterImpl> implements MvpViewImpl<ImagesBean>{
    private ProgressDialog progressDialog;
    private RecyclerView recyclerView;
    private ImagesAdapter imagesAdapter;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void iniViews() {
        recyclerView = findViewById(R.id.main_recyclerView);
        imagesAdapter = new ImagesAdapter(this, null);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
        recyclerView.setAdapter(imagesAdapter);

        progressDialog = new ProgressDialog(this);
        progressDialog.setCancelable(false);
        progressDialog.setMessage("正在拼命加载中...");
    }

    @Override
    protected void getRequestDatas() {
        //加载数据
        getDatas();
    }

    @Override
    protected MvpPresenterImpl createPresenter() {
        return new MvpPresenterImpl();
    }

    private void getDatas() {
        getPresenter().requestNetDatas("福利", "10", "1");
    }

    @Override
    public void showProgressDialog() {
        if(!progressDialog.isShowing()){
            progressDialog.show();
        }
    }

    @Override
    public void hideProgressDialog() {
        if(progressDialog.isShowing()){
            progressDialog.dismiss();
        }
    }

    @Override
    public void loadSuccess(ImagesBean data) {
        imagesAdapter.setDatas(data.getResults());
    }

    @Override
    public void loadFailure(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
}

当然了,这里只是加载图片,业务比较单一,在activity中直接实现了MvpViewImpl接口,直接明确了泛型类型为ImagesBean实体,业务比较复杂的时候,可能一个页面需要不同的接口得到不同的数据类型,其实我们可以直接这样

public interface MvpViewImpl extends BaseMvpView {
    //获取到广告位数据
    void loadBannerSuccess(BannerBean bean);
    //获取到推荐数据
    void loadRecommendSuccess(RecommendBean recommendBean);
}

到此为止简单的封装就完成了,本示例业务比较简单,对于真正项目的话可以视情况扩展。这段时间无意间看到了一个封装的非常不错的一个库,打算利用业余时间好好研究下这个库。下面提供下这个库的地址,大家有兴趣的话也可以去研究研究https://github.com/JessYanCoding/MVPArms

猜你喜欢

转载自blog.csdn.net/xiaxiazaizai01/article/details/79209288