androidMVP

Android---我所理解的MVP模式

2018-08-21  本文已影响291人  海阔sun天空

前言

两年前第一次接触MVP模式,就被各种接口各种分层给弄的云里雾里。相信大多数的朋友第一次接触MVP的实例就是网上泛滥的那个登录模型,没错,就是这个没有任何卵用的模型让我认识到最初的MVP模式。随着这两年频繁的接触MVP,对这种质壁分离的模式也有了些小小的见解,下面就来分析一下。

模型简介

MVP模式简单的理解可以概括为将一个业务拆分成Model,View,Presenter三层结构。

Model层主管数据处理,包括网络数据,数据库数据,文件IO读写等;
View层主管UI更新和与UI相关的简单逻辑;
Presenter就是连接Model层和View层之间的桥梁,沟通Model和View进行各种交流,其作用一方面是获取Model层数据,二就是通过拿到的数据,进行部分逻辑处理后,交由View层进行UI更新。

这篇文章需要有一定的MVP基础,不然你可能会懵逼

乞丐版MVP

我这里将未经封装的MVP模式简称为乞丐版MVP,这种MVP只是简单的使用了MVP的思想,粗暴的写出来M,V,P三层结构,未经封装,存在大量的代码冗余和部分内存泄漏的风险,所以乞丐版MVP只是用来理解MVP模式,不能直接放在项目中使用。



简单模拟一个场景:假设在一个Activity中有一个网络请求,拿到网络数据后在P层进行相关逻辑处理后,在V层进行相应数据更新。
我们从M层先写,一步一步倒追到V层,首先M层有一个网络请求:

public class DemoModel {
    public void onRequestData(final CallBack<DemoBean> callBack) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟网络请求
                SystemClock.sleep(5000);
                //网络请求成功
                callBack.onSuccess(new DemoBean("哈哈"));
            }
        }).start();
    }
}

根据MVP的思想,V层和M层不能有直接的交流,那么肯定就是P层来进行调用M层的代码:

public class DemoPresenter {

    private IDemoView mView;
    private DemoModel mModel;

    public DemoPresenter(IDemoView view) {
        mView = view;
        mModel = new DemoModel();
    }


    public void onRequestData() {
        mModel.onRequestData(new CallBack<DemoBean>() {
            @Override
            public void onSuccess(DemoBean data) {
                mView.updateUI(data);
            }
        });
    }
}

V层和P层直接是通过接口的形式IDemoView进行交互,当然P层和M层也可以使用接口交互:

public interface IDemoView {

    void updateUI(DemoBean data);
}

V层也就是相当于我们的Activity:

public class DemoActivity extends AppCompatActivity implements IDemoView{

    private DemoPresenter mPresenter = new DemoPresenter(this);

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter.onRequestData();
    }

    @Override
    public void updateUI(DemoBean data) {
        Toast.makeText(this, data.str, Toast.LENGTH_SHORT).show();
    }
}

这就是一个最基本的MVP结构,V层和M层阻隔,P作为中枢系统,将M层拿到的数据通过一定的逻辑处理后,交由V层进行UI更新。
乞丐版MVP有以下几个缺点:

1.V中使用P都需要new一个出来,不同的V使用的P不同,但创建形式相同,代码存在冗余;
2.P中使用M也是在构造器中new一个M,M作为网络请求,可能会存在多种场景复用的情况,这种写法阻碍了代码的复用;
3.V层被干掉,P层却没有收到相应关闭的通知,存在一定的内存泄漏;

封装后的MVP框架

针对于乞丐版MVP,这里我做了以下的几点封装:

1.创建V,P,M层对应的基类,如:BaseMVPActivity,BasePresenter,BaseModel;
2.使用注解的形式,在BaseMVPActivity中自动创建出我们所需要的P,减少代码冗余;
3.创建一个ModelManager管理类,使用反射的形式创建出Model,减少P和M之间的耦合,增强复用性;
4.P层中增加其生命周期方法,绑定于对应的V层,V层被干掉的情况下,保证P也会结束掉相应的工作,减少内存泄漏;
5.使用Loader自动管理P,防止在屏幕状态改变的情况下,创建多个P。

先看一个结构图: MVP框架流程图.jpg

结构图上可以清晰的看到调用情况,V层通过注解的形式创建P,P通过IBaseView通知V,P通过Token类加载ModelManager获取对应的M对象,ModelManager通过CallBack回调来通知P拿取数据,ModelManager使用反射的形式管理众多M,这样就成功的将M层单独抽离成一个大模块。

废话不多说,首先先看下使用情况:
V层:

@CreatePresenter(MainPresenter.class)
public class MainActivity extends BaseMVPActivity<MainPresenter> implements IMainView {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onPresenterCreate() {
        mPresenter.show();
    }

    @Override
    public void show() {
        Log.d("MainActivity", "呵呵");
    }
}

P层:

public class MainPresenter extends BasePresenter<IMainView> {

    public void show() {
        HttpParam param = HttpParam.obtain();
        param.put(APIParamKey.PHONE, "13112345678");

        ModelManager.getModel(Token.MAIN_MODE)
                .setParam(param)
                .execute(new CallBack<MainBean>() {
                    @Override
                    public void onSuccess(MainBean data) {
                           mView.show();
                    }

                    @Override
                    public void onFailure(String msg) {

                    }
                });
    }
}

M层:

public class MainModel extends BaseModel<MainBean> {

    @Override
    public void execute(final CallBack<MainBean> callBack) {
        super.execute(callBack);
        HttpParam param = getParam();
        String phone = param.get(APIParamKey.PHONE);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                callBack.onSuccess(new MainBean());
            }
        }, 4000);
    }
}

封装后的结构比之前要清晰许多,代码也少了一部分,M层和P层也实现了完全的解耦合。
那么我们来先看看M层和P层是怎么样实现完全的解耦合的呢:

M层主要封装:

ModelManager:

public class ModelManager {

    private static ArrayMap<String, IBaseModel> mCache = new ArrayMap<>();

    /**
     * 传递类,反射获得该类对象
     *
     * @param token 类引用字符串
     */
    public static IBaseModel getModel(Class<? extends BaseModel> token) {
        return getModel(token.getName());
    }

    /**
     * 传递类引用字符串,反射获得该类对象
     *
     * @param token 类引用字符串
     */
    public static IBaseModel getModel(String token) {
        IBaseModel model = mCache.get(token);
        try {
            if (model == null) {
                model = (IBaseModel) Class.forName(token).newInstance();
                mCache.put(token, model);
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return model;
    }
}

主要是使用反射来获取对应的Model类,增加mCache缓存实现容器类单例。这里我实现了两个方法,一个是传递Class类,一个是传递字符串。我个人建议是使用传递字符串的方式来创建Model,通过一个类Token,封装所有的Model信息,使P和M直接只通过ModelManager的形式来交互,也便于M的复用。

ModelManager第二个方法是传递一个类的引用字符串,我把所有要引用的字符串写到一个类里,便于管理,起名叫Token:

public interface Token {
    //    String MAIN_MODE = "com.zdu.mvpdemo.MainModel";
    String MAIN_MODE = MainModel.class.getName();
}

我写了两种方式来引用字符串,推荐使用第二种,使用XXXModel.class.getName()的方式来获取当前类的引用。让Token和各个Model直接建立起联系,便于查找,同时也能防止混淆时Model类因为没有任何引用而被认为是无效类从而被删掉的问题。

BaseModel类中主要实现了4个方法:

参数的set和get方法:

 @Override
    public BaseModel<T> setParam(HttpParam param) {
        this.mParam = param;
        mParam.setUse(true);
        return this;
    }

    @Override
    public HttpParam getParam() {
        if (mParam == null) {
            throw new NullPointerException("入参为空");
        }
        return mParam;
    }

HttpParam类是我封装的一个ArrayMap集合,内部加入了缓冲池的概念。优点就是可以重复利用,缺点就是无法动态的指定Map集合的大小。至于怎么实现的缓存池,请看我写的另一篇文章: Android 模拟Message.obtain(),构建自己的缓存池

两个执行方法 excute(CallBack<T>),和空参excute()

/**
     * 执行异步操作
     *
     * @param callBack<T> 回调
     */
    @Override
    public void execute(@Nullable CallBack<T> callBack) {
        recycleParam();
    }

    /**
     * 执行异步操作,无需回调
     */
    @Override
    public void execute() {
        execute(null);
    }
   
    /**
     * 回收
     */
    private void recycleParam() {
        if (mParam != null) {
            mParam.setUse(false);
            mParam.recycle();
        }
    }

两个方法我都没设置成抽象的,简单实现了下回收的方法,一个带回调,一个不带回调。子类想调用哪个就实现哪个方法就可以了

V层主要封装:

V层主要实现BaseMVPActivity,封装了以下几个功能:

基类自动与P层进行关联,并进行生命周期绑定
注解@CreatePresenter的形式自动创建P
使用Loader管理P的加载,防止屏幕状态改变时,创建多个P

BaseMVPActivity:

public class BaseMVPActivity<P extends BasePresenter> extends BaseActivity implements LoaderManager.LoaderCallbacks<P>, IBaseView {

    private final int LOADER_ID = TAG.hashCode();
    protected P mPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportLoaderManager().initLoader(LOADER_ID, savedInstanceState, this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mPresenter != null) {
            mPresenter.onPause();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mPresenter != null) {
            mPresenter.onResume();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.onDetach();
        }
        mPresenter = null;
    }

    @NonNull
    @Override
    public Loader<P> onCreateLoader(int id, @Nullable Bundle args) {
        return new PresenterLoader<>(this, getClass());
    }

    @Override
    public void onLoadFinished(@NonNull Loader<P> loader, P data) {
        if (data != null) {
            mPresenter = data;
            mPresenter.onAttach(this);
            onPresenterCreate();
        }
    }

    @Override
    public void onLoaderReset(@NonNull Loader<P> loader) {
        if (mPresenter != null) {
            mPresenter.onDetach();
        }
        mPresenter = null;
    }

    /**
     * 回调此方法,presenter创建完毕
     */
    protected void onPresenterCreate() {

    }

}

首先在onCreate方法初始化Loader,在实现的onCreateLoader方法里加载注解解析器PresenterLoader,加载完成实现方法onLoadFinished进行P的赋值和接口的绑定,数据被重置时实现onLoaderReset,接触对P的绑定,对应的生命周期方法里也将P绑定在一起。

注解解析器PresenterLoader:

public class PresenterLoader<P extends BasePresenter> extends Loader<P> {

    private CreatePresenter mCreatePresenter;
    private P mPresenter;

    public PresenterLoader(Context context, Class<?> tClass) {
        super(context);
        mCreatePresenter = tClass.getAnnotation(CreatePresenter.class);
    }

    private P getPresenter() {
        if (mCreatePresenter != null) {
            Class<P> pClass = (Class<P>) mCreatePresenter.value();
            try {
                return pClass.newInstance();
            } catch (Exception e) {
                throw new RuntimeException("Presenter创建失败!,检查是否声明了@CreatePresenter(xx.class)注解");
            }
        }
        return null;
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        if (mPresenter == null) {
            forceLoad();
        } else {
            deliverResult(mPresenter);
        }
    }

    @Override
    protected void onForceLoad() {
        super.onForceLoad();
        mPresenter = getPresenter();
        deliverResult(mPresenter);
    }
}

这个类的作用其一就是绑定Loader,管理P的创建加载情况,其二就是获取注解对象,通过反射来创建P。

注解类就很简单了:

@Retention(RetentionPolicy.RUNTIME)
public @interface CreatePresenter {

    Class<? extends BasePresenter> value();
}

有一定的约束条件,必须继承BasePresenter才能通过注解来创建。

P层的封装:

V和M都搞定了,作为桥梁的P层就简单了:
BasePresenter:

public class BasePresenter<V extends IBaseView> implements IBasePresenter<V> {

    protected V mView;

    @Override
    public void onAttach(V view) {
        mView = view;
    }

    @Override
    public V getView() {
        checkViewAttached();
        return mView;
    }

    @Override
    public void onPause() {
    }

    @Override
    public void onResume() {
    }

    @Override
    public boolean isViewAttached() {
        return mView != null;
    }

    @Override
    public void checkViewAttached() {
        if (!isViewAttached()) {
            throw new NullPointerException("View 已为空");
        }
    }

    /**
     * 取消,置空数据,防止内存泄露
     */
    @Override
    public void onDetach() {
        mView = null;
    }

}

自动装箱:onAttach(),自动拆箱:onDetach(),还有各种生命周期,逻辑处理起来就方便了很多。

综述

通过一定的封装,创建P的过程交给基类通过注解的形式来创建,减少了代码的冗余;给P一定的生命周期方法,在V被干掉的情况下,P能及时处理对应的事件,减少内存泄漏;通过ModelManager统一管理Model,使用Token类作为Model和ModelManager的桥梁,彻底的将P和M进行解耦,同时分离了M层,使M层抽出成一个独立的模块,增加了M的复用性。
附上Demo的GitHub地址:MVPDemo

上一篇下一篇

猜你喜欢

热点阅读