前言
兩年前第一次接觸MVP模式,就被各種接口各種分層給弄的云里霧里爷速。相信大多數(shù)的朋友第一次接觸MVP的實例就是網(wǎng)上泛濫的那個登錄模型挖胃,沒錯,就是這個沒有任何卵用的模型讓我認(rèn)識到最初的MVP模式持搜。隨著這兩年頻繁的接觸MVP,對這種質(zhì)壁分離的模式也有了些小小的見解焙矛,下面就來分析一下葫盼。
模型簡介
MVP模式簡單的理解可以概括為將一個業(yè)務(wù)拆分成Model,View村斟,Presenter三層結(jié)構(gòu)贫导。
Model層主管數(shù)據(jù)處理,包括網(wǎng)絡(luò)數(shù)據(jù)蟆盹,數(shù)據(jù)庫數(shù)據(jù)脱盲,文件IO讀寫等;
View層主管UI更新和與UI相關(guān)的簡單邏輯日缨;
Presenter就是連接Model層和View層之間的橋梁,溝通Model和View進(jìn)行各種交流掖看,其作用一方面是獲取Model層數(shù)據(jù)匣距,二就是通過拿到的數(shù)據(jù),進(jìn)行部分邏輯處理后哎壳,交由View層進(jìn)行UI更新毅待。
這篇文章需要有一定的MVP基礎(chǔ),不然你可能會懵逼
乞丐版MVP
我這里將未經(jīng)封裝的MVP模式簡稱為乞丐版MVP归榕,這種MVP只是簡單的使用了MVP的思想尸红,粗暴的寫出來M,V刹泄,P三層結(jié)構(gòu)外里,未經(jīng)封裝,存在大量的代碼冗余和部分內(nèi)存泄漏的風(fēng)險特石,所以乞丐版MVP只是用來理解MVP模式盅蝗,不能直接放在項目中使用。
簡單模擬一個場景:假設(shè)在一個Activity中有一個網(wǎng)絡(luò)請求姆蘸,拿到網(wǎng)絡(luò)數(shù)據(jù)后在P層進(jìn)行相關(guān)邏輯處理后墩莫,在V層進(jìn)行相應(yīng)數(shù)據(jù)更新芙委。
我們從M層先寫,一步一步倒追到V層狂秦,首先M層有一個網(wǎng)絡(luò)請求:
public class DemoModel {
public void onRequestData(final CallBack<DemoBean> callBack) {
new Thread(new Runnable() {
@Override
public void run() {
//模擬網(wǎng)絡(luò)請求
SystemClock.sleep(5000);
//網(wǎng)絡(luò)請求成功
callBack.onSuccess(new DemoBean("哈哈"));
}
}).start();
}
}
根據(jù)MVP的思想灌侣,V層和M層不能有直接的交流,那么肯定就是P層來進(jìn)行調(diào)用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進(jìn)行交互裂问,當(dāng)然P層和M層也可以使用接口交互:
public interface IDemoView {
void updateUI(DemoBean data);
}
V層也就是相當(dāng)于我們的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結(jié)構(gòu)侧啼,V層和M層阻隔,P作為中樞系統(tǒng)愕秫,將M層拿到的數(shù)據(jù)通過一定的邏輯處理后慨菱,交由V層進(jìn)行UI更新。
乞丐版MVP有以下幾個缺點:
1.V中使用P都需要new一個出來戴甩,不同的V使用的P不同符喝,但創(chuàng)建形式相同,代碼存在冗余甜孤;
2.P中使用M也是在構(gòu)造器中new一個M协饲,M作為網(wǎng)絡(luò)請求,可能會存在多種場景復(fù)用的情況缴川,這種寫法阻礙了代碼的復(fù)用茉稠;
3.V層被干掉,P層卻沒有收到相應(yīng)關(guān)閉的通知把夸,存在一定的內(nèi)存泄漏而线;
封裝后的MVP框架
針對于乞丐版MVP,這里我做了以下的幾點封裝:
先看一個結(jié)構(gòu)圖:1.創(chuàng)建V恋日,P膀篮,M層對應(yīng)的基類,如:BaseMVPActivity岂膳,BasePresenter誓竿,BaseModel;
2.使用注解的形式谈截,在BaseMVPActivity中自動創(chuàng)建出我們所需要的P筷屡,減少代碼冗余;
3.創(chuàng)建一個ModelManager管理類簸喂,使用反射的形式創(chuàng)建出Model毙死,減少P和M之間的耦合,增強(qiáng)復(fù)用性喻鳄;
4.P層中增加其生命周期方法规哲,綁定于對應(yīng)的V層,V層被干掉的情況下诽表,保證P也會結(jié)束掉相應(yīng)的工作唉锌,減少內(nèi)存泄漏隅肥;
5.使用Loader自動管理P,防止在屏幕狀態(tài)改變的情況下袄简,創(chuàng)建多個P腥放。
結(jié)構(gòu)圖上可以清晰的看到調(diào)用情況,V層通過注解的形式創(chuàng)建P绿语,P通過IBaseView通知V秃症,P通過Token類加載ModelManager獲取對應(yīng)的M對象,ModelManager通過CallBack回調(diào)來通知P拿取數(shù)據(jù)吕粹,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);
}
}
封裝后的結(jié)構(gòu)比之前要清晰許多聚请,代碼也少了一部分,M層和P層也實現(xiàn)了完全的解耦合稳其。
那么我們來先看看M層和P層是怎么樣實現(xiàn)完全的解耦合的呢:
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;
}
}
主要是使用反射來獲取對應(yīng)的Model類既鞠,增加mCache緩存實現(xiàn)容器類單例煤傍。這里我實現(xiàn)了兩個方法,一個是傳遞Class類嘱蛋,一個是傳遞字符串蚯姆。我個人建議是使用傳遞字符串的方式來創(chuàng)建Model,通過一個類Token洒敏,封裝所有的Model信息蒋失,使P和M直接只通過ModelManager的形式來交互,也便于M的復(fù)用桐玻。
ModelManager第二個方法是傳遞一個類的引用字符串,我把所有要引用的字符串寫到一個類里荆萤,便于管理镊靴,起名叫Token:
public interface Token {
// String MAIN_MODE = "com.zdu.mvpdemo.MainModel";
String MAIN_MODE = MainModel.class.getName();
}
我寫了兩種方式來引用字符串,推薦使用第二種链韭,使用XXXModel.class.getName()的方式來獲取當(dāng)前類的引用偏竟。讓Token和各個Model直接建立起聯(lián)系,便于查找敞峭,同時也能防止混淆時Model類因為沒有任何引用而被認(rèn)為是無效類從而被刪掉的問題踊谋。
BaseModel類中主要實現(xiàn)了4個方法:
參數(shù)的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("入?yún)榭?);
}
return mParam;
}
HttpParam類是我封裝的一個ArrayMap集合,內(nèi)部加入了緩沖池的概念旋讹。優(yōu)點就是可以重復(fù)利用殖蚕,缺點就是無法動態(tài)的指定Map集合的大小轿衔。至于怎么實現(xiàn)的緩存池,請看我寫的另一篇文章: Android 模擬Message.obtain()睦疫,構(gòu)建自己的緩存池害驹。
兩個執(zhí)行方法 excute(CallBack<T>),和空參excute()
/**
* 執(zhí)行異步操作
*
* @param callBack<T> 回調(diào)
*/
@Override
public void execute(@Nullable CallBack<T> callBack) {
recycleParam();
}
/**
* 執(zhí)行異步操作蛤育,無需回調(diào)
*/
@Override
public void execute() {
execute(null);
}
/**
* 回收
*/
private void recycleParam() {
if (mParam != null) {
mParam.setUse(false);
mParam.recycle();
}
}
兩個方法我都沒設(shè)置成抽象的宛官,簡單實現(xiàn)了下回收的方法,一個帶回調(diào)瓦糕,一個不帶回調(diào)底洗。子類想調(diào)用哪個就實現(xiàn)哪個方法就可以了
V層主要封裝:
V層主要實現(xiàn)BaseMVPActivity,封裝了以下幾個功能:
基類自動與P層進(jìn)行關(guān)聯(lián)咕娄,并進(jìn)行生命周期綁定
注解@CreatePresenter的形式自動創(chuàng)建P
使用Loader管理P的加載亥揖,防止屏幕狀態(tài)改變時,創(chuàng)建多個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;
}
/**
* 回調(diào)此方法谭胚,presenter創(chuàng)建完畢
*/
protected void onPresenterCreate() {
}
}
首先在onCreate方法初始化Loader徐块,在實現(xiàn)的onCreateLoader方法里加載注解解析器PresenterLoader,加載完成實現(xiàn)方法onLoadFinished進(jìn)行P的賦值和接口的綁定灾而,數(shù)據(jù)被重置時實現(xiàn)onLoaderReset胡控,接觸對P的綁定,對應(yīng)的生命周期方法里也將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創(chuàng)建失敗!昼激,檢查是否聲明了@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的創(chuàng)建加載情況锡搜,其二就是獲取注解對象橙困,通過反射來創(chuàng)建P。
注解類就很簡單了:
@Retention(RetentionPolicy.RUNTIME)
public @interface CreatePresenter {
Class<? extends BasePresenter> value();
}
有一定的約束條件耕餐,必須繼承BasePresenter才能通過注解來創(chuàng)建凡傅。
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 已為空");
}
}
/**
* 取消肠缔,置空數(shù)據(jù)夏跷,防止內(nèi)存泄露
*/
@Override
public void onDetach() {
mView = null;
}
}
自動裝箱:onAttach(),自動拆箱:onDetach()明未,還有各種生命周期槽华,邏輯處理起來就方便了很多。
綜述
通過一定的封裝趟妥,創(chuàng)建P的過程交給基類通過注解的形式來創(chuàng)建猫态,減少了代碼的冗余;給P一定的生命周期方法,在V被干掉的情況下亲雪,P能及時處理對應(yīng)的事件勇凭,減少內(nèi)存泄漏;通過ModelManager統(tǒng)一管理Model匆光,使用Token類作為Model和ModelManager的橋梁套像,徹底的將P和M進(jìn)行解耦,同時分離了M層终息,使M層抽出成一個獨立的模塊夺巩,增加了M的復(fù)用性。
附上Demo的GitHub地址:MVPDemo周崭。