一补憾、前言
首先聲明一下叛拷,沒有完美的架構(gòu),只要適合自己的項(xiàng)目浴鸿,那就是最好的架構(gòu)井氢。
本例子是MVP + Retrofit + RxJava結(jié)合的例子,但本文的重點(diǎn)在于講解MVP架構(gòu)岳链,所以涉及Retrofit和RxJava的部分將直接略過花竞,默認(rèn)讀者已了解這兩部分內(nèi)容,如有需要掸哑,請(qǐng)自行查閱相關(guān)資料约急,網(wǎng)上資料很多。
史上最全MVP資料合集: Android MVP 詳解(上)
RxJava學(xué)習(xí)參考資料: 是時(shí)候?qū)W習(xí)RxJava了
二苗分、MVC
早期項(xiàng)目中厌蔽,我們會(huì)使用MVC架構(gòu)來構(gòu)建我們的項(xiàng)目,但是MVC架構(gòu)的缺陷很明顯俭嘁,V層和C層的職責(zé)混淆不清躺枕,很容易就會(huì)寫成萬(wàn)能的Activity服猪,把業(yè)務(wù)邏輯供填、View操作等一系列功能全放到Activity中來實(shí)現(xiàn)拐云。
三、MVP
MVP是MVC的進(jìn)化版近她,它把Controller的職責(zé)從Activity/Fragment中拆分出來叉瘩,作為Presenter,這樣就實(shí)現(xiàn)了Activity/Fragment和業(yè)務(wù)邏輯的解耦粘捎,更好地解決了數(shù)據(jù)與界面的關(guān)系薇缅。
- View層: 對(duì)應(yīng)于Activity/Fragment,負(fù)責(zé)View的繪制以及與用戶交互
- Presenter層: 負(fù)責(zé)完成View與Model間的交互
- Model層: 實(shí)體模型攒磨、與數(shù)據(jù)進(jìn)行交互泳桦,對(duì)數(shù)據(jù)進(jìn)行加工處理
1. 架構(gòu)圖
(上圖由ProcessOn在線工具繪制)
2. 類圖
(上圖由StarUML繪制)
四、MVP實(shí)踐
1. 兩個(gè)基類接口
首先定義兩個(gè)接口娩缰,這兩個(gè)接口分別是所有View和Presenter的基類: IBaseView
和IBasePresenter
灸撰。
-
IBaseView
中主要定義一些通用的界面方法,如顯示/隱藏進(jìn)度條拼坎、顯示提示信息等浮毯。 -
IBasePresenter
中也可以定義一些通用的方法,如初始化方法等泰鸡。
public interface IBaseView {
void showLoading();
void hideLoading();
void showMessage(String msg);
}
public interface IBasePresenter {
...
}
2. 定義契約類(接口)
使用契約類來統(tǒng)一管理View與Presenter的所有接口债蓝,這種方式使得View與Presenter中有哪些功能,一目了然盛龄,維護(hù)起來也很方便饰迹。
public interface CookDetailContract {
interface IView extends IBaseView {
void updateCookDetail(CookDetail cookDetail);
}
interface IPresenter extends IBasePresenter {
void getCookDetail(String apikey, String id);
}
}
-
CookDetailContract
中的IView
接口定義了該界面(功能)中所有的UI狀態(tài)情況,MainAcitivty作為View層余舶,實(shí)現(xiàn)了該接口蹦锋,這樣MainActivity
就只關(guān)注UI相關(guān)的狀態(tài)更新。 -
IPresenter
接口則定義了該界面(功能)中所有的用戶操作事件欧芽,CookDetailPresenter
作為Presenter層莉掂,實(shí)現(xiàn)了該接口,這樣CookDetailPresenter
就只關(guān)注業(yè)務(wù)層的相關(guān)邏輯千扔,UI的更新只需調(diào)用IView
的狀態(tài)方法憎妙。
3. View層(Activity/Fragment)
Activity/Fragment是一個(gè)全局的控制者,負(fù)責(zé)創(chuàng)建View以及Presenter實(shí)例曲楚,并將二者聯(lián)系起來厘唾。
在本例中,MainActivity實(shí)現(xiàn)了CookDetailContract.IView
接口龙誊,并在onResume()回調(diào)中創(chuàng)建CookDetailPresenter
實(shí)例抚垃,CookDetailPresenter的構(gòu)造函數(shù)中實(shí)現(xiàn)了View和Presenter的關(guān)聯(lián)。
在創(chuàng)建完P(guān)resenter后,調(diào)用Presenter的getCookDetail()方法獲取相應(yīng)的數(shù)據(jù)(如上圖步驟①)鹤树。
在獲取到Model層的數(shù)據(jù)后铣焊,Presenter通過IView中的updateCookDetail()方法返回?cái)?shù)據(jù)(如上圖步驟④),Activity獲取數(shù)據(jù)后罕伯,將結(jié)果展示到界面上反饋給用戶曲伊。
mCookDetailPresenter = new CookDetailPresenter(MainActivity.this, this);
mCookDetailPresenter.getCookDetail(Config.API_KEY, (id++) + "");
@Override
public void updateCookDetail(CookDetail cookDetail) {
tvName.setText(cookDetail.getName());
Picasso.with(this).load(Config.IMAGE_URL_PREFIX + cookDetail.getImg()).into(ivImage);
}
4. Presenter層
它實(shí)現(xiàn)了契約類中的IPresenter
接口。
Presenter翻譯過來是主持人的意思追他,它做為MVP架構(gòu)中最關(guān)鍵的一層坟募,負(fù)責(zé)連接View層和Model層。比如控制顯示/隱藏進(jìn)度框邑狸、顯示/隱藏空布局懈糯、錯(cuò)誤布局,調(diào)用相應(yīng)的Model層方法進(jìn)行數(shù)據(jù)的獲取单雾,并在Model層返回?cái)?shù)據(jù)后昂利,將數(shù)據(jù)適配到View中展示。這樣铁坎,便可以讓Model層只關(guān)注數(shù)據(jù)相關(guān)的操作蜂奸、也讓View層只專注于界面的展示,讓各個(gè)層級(jí)各司其職硬萍,相互協(xié)作扩所。
public class CookDetailPresenter implements CookDetailContract.IPresenter {
private Context mContext;
private CookDetailContract.IView mView;
private CookDetailManager mCookDetailManager = CookDetailManager.getInstance();
public CookDetailPresenter(Context context, CookDetailContract.IView view) {
this.mContext = context;
this.mView = view;
}
@Override
public void getCookDetail(String apikey, String id) {
mView.showLoading();
mCookDetailManager.getCookDetail(apikey, id, new Callback<CookDetail>() {
@Override
public void onSuccess(CookDetail object) {
mView.updateCookDetail(object);
mView.hideLoading();
}
@Override
public void onFail(int errorNo, String errorMsg) {
ErrorUtil.processErrorMessage(mContext, errorNo, errorMsg, mView);
mView.hideLoading();
}
});
}
}
5. Model層
Model層不只包含實(shí)體對(duì)象,更主要的功能是處理一切與數(shù)據(jù)相關(guān)的操作朴乖,如數(shù)據(jù)的獲取祖屏、存儲(chǔ)、數(shù)據(jù)狀態(tài)變化都是Model層的任務(wù)买羞,Presenter會(huì)根據(jù)需要調(diào)用該層的數(shù)據(jù)處理邏輯(如上圖步驟②)袁勺,如有需要,Model層會(huì)使用回調(diào)將數(shù)據(jù)傳回Presenter層(如上圖步驟③)畜普。
public class CookDetailManager {
private volatile static CookDetailManager instance;
private CookDetailManager() {
}
public static CookDetailManager getInstance() {
if (instance == null) {
synchronized (CookDetailManager.class) {
if (instance == null) {
instance = new CookDetailManager();
}
}
}
return instance;
}
public void getCookDetail(String apikey, String id, final Callback<CookDetail> callback) {
if (callback == null) {
return;
}
Retrofit retrofit = RetrofitClient.INSTANCE.getRetrofit();
ApiService apiService = retrofit.create(ApiService.class);
Observable<CookDetail> observable = apiService.getCookDetail(apikey, id);
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<CookDetail>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
callback.onFail(Constants.ErrorNo.ServerError, "");
e.printStackTrace();
}
@Override
public void onNext(CookDetail respEntity) {
callback.onSuccess(respEntity);
}
});
}
}
五期丰、總結(jié)
使用MVP架構(gòu),缺點(diǎn)在于需要增加很多接口類吃挑、實(shí)現(xiàn)類钝荡,對(duì)于剛開始接口MVP架構(gòu)的人來說,增加了不少的學(xué)習(xí)成本舶衬,看著一堆的類埠通、一堆的接口,調(diào)來調(diào)去的逛犹,剛開始肯定會(huì)看暈端辱。
但是當(dāng)你熟悉了MVP架構(gòu)梁剔,并掌握了它的精髓后,會(huì)發(fā)現(xiàn)雖然增加了很多代碼舞蔽,但是整體架構(gòu)變得非常清晰荣病,代碼也可以多處復(fù)用。各個(gè)類和層的職責(zé)都非常明確且單一喷鸽,后期的擴(kuò)展众雷,維護(hù)都會(huì)更加容易灸拍。整體的可測(cè)試性非常的好做祝,UI層和業(yè)務(wù)層可以分別進(jìn)行單元測(cè)試。
項(xiàng)目代碼已共享到Github:AndroidMVPArchitecture
六鸡岗、效果圖
六混槐、參考資料
Android官方MVP架構(gòu)項(xiàng)目解析
Android:“萬(wàn)能”Activity重構(gòu)篇-牛曉偉
Android高仿微信之mvp實(shí)現(xiàn)(一)
RxJava 與 Retrofit 結(jié)合的最佳實(shí)踐
Android MVP 實(shí)戰(zhàn)經(jīng)驗(yàn)
PS:歡迎關(guān)注SherlockShi博客