Android---我所理解的MVP模式
前言
两年前第一次接触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,这里我做了以下的几点封装:
先看一个结构图: MVP框架流程图.jpg1.创建V,P,M层对应的基类,如:BaseMVPActivity,BasePresenter,BaseModel;
2.使用注解的形式,在BaseMVPActivity中自动创建出我们所需要的P,减少代码冗余;
3.创建一个ModelManager管理类,使用反射的形式创建出Model,减少P和M之间的耦合,增强复用性;
4.P层中增加其生命周期方法,绑定于对应的V层,V层被干掉的情况下,保证P也会结束掉相应的工作,减少内存泄漏;
5.使用Loader自动管理P,防止在屏幕状态改变的情况下,创建多个P。
结构图上可以清晰的看到调用情况,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。