一、前言
首先聲明一下,沒有完美的架構,只要適合自己的項目,那就是最好的架構。
本例子是MVP + Retrofit + RxJava結合的例子,但本文的重點在于講解MVP架構,所以涉及Retrofit和RxJava的部分將直接略過,默認讀者已了解這兩部分內容,如有需要,請自行查閱相關資料,網上資料很多。
史上最全MVP資料合集: Android MVP 詳解(上)
RxJava學習參考資料: 是時候學習RxJava了
二、MVC
早期項目中,我們會使用MVC架構來構建我們的項目,但是MVC架構的缺陷很明顯,V層和C層的職責混淆不清,很容易就會寫成萬能的Activity,把業務邏輯、View操作等一系列功能全放到Activity中來實現。
三、MVP
MVP是MVC的進化版,它把Controller的職責從Activity/Fragment中拆分出來,作為Presenter,這樣就實現了Activity/Fragment和業務邏輯的解耦,更好地解決了數據與界面的關系。
- View層: 對應于Activity/Fragment,負責View的繪制以及與用戶交互
- Presenter層: 負責完成View與Model間的交互
- Model層: 實體模型、與數據進行交互,對數據進行加工處理
1. 架構圖
(上圖由ProcessOn在線工具繪制)
2. 類圖
(上圖由StarUML繪制)
四、MVP實踐
1. 兩個基類接口
首先定義兩個接口,這兩個接口分別是所有View和Presenter的基類: IBaseView
和IBasePresenter
。
-
IBaseView
中主要定義一些通用的界面方法,如顯示/隱藏進度條、顯示提示信息等。 -
IBasePresenter
中也可以定義一些通用的方法,如初始化方法等。
public interface IBaseView {
void showLoading();
void hideLoading();
void showMessage(String msg);
}
public interface IBasePresenter {
...
}
2. 定義契約類(接口)
使用契約類來統一管理View與Presenter的所有接口,這種方式使得View與Presenter中有哪些功能,一目了然,維護起來也很方便。
public interface CookDetailContract {
interface IView extends IBaseView {
void updateCookDetail(CookDetail cookDetail);
}
interface IPresenter extends IBasePresenter {
void getCookDetail(String apikey, String id);
}
}
-
CookDetailContract
中的IView
接口定義了該界面(功能)中所有的UI狀態情況,MainAcitivty作為View層,實現了該接口,這樣MainActivity
就只關注UI相關的狀態更新。 -
IPresenter
接口則定義了該界面(功能)中所有的用戶操作事件,CookDetailPresenter
作為Presenter層,實現了該接口,這樣CookDetailPresenter
就只關注業務層的相關邏輯,UI的更新只需調用IView
的狀態方法。
3. View層(Activity/Fragment)
Activity/Fragment是一個全局的控制者,負責創建View以及Presenter實例,并將二者聯系起來。
在本例中,MainActivity實現了CookDetailContract.IView
接口,并在onResume()回調中創建CookDetailPresenter
實例,CookDetailPresenter的構造函數中實現了View和Presenter的關聯。
在創建完Presenter后,調用Presenter的getCookDetail()方法獲取相應的數據(如上圖步驟①)。
在獲取到Model層的數據后,Presenter通過IView中的updateCookDetail()方法返回數據(如上圖步驟④),Activity獲取數據后,將結果展示到界面上反饋給用戶。
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層
它實現了契約類中的IPresenter
接口。
Presenter翻譯過來是主持人的意思,它做為MVP架構中最關鍵的一層,負責連接View層和Model層。比如控制顯示/隱藏進度框、顯示/隱藏空布局、錯誤布局,調用相應的Model層方法進行數據的獲取,并在Model層返回數據后,將數據適配到View中展示。這樣,便可以讓Model層只關注數據相關的操作、也讓View層只專注于界面的展示,讓各個層級各司其職,相互協作。
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層不只包含實體對象,更主要的功能是處理一切與數據相關的操作,如數據的獲取、存儲、數據狀態變化都是Model層的任務,Presenter會根據需要調用該層的數據處理邏輯(如上圖步驟②),如有需要,Model層會使用回調將數據傳回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);
}
});
}
}
五、總結
使用MVP架構,缺點在于需要增加很多接口類、實現類,對于剛開始接口MVP架構的人來說,增加了不少的學習成本,看著一堆的類、一堆的接口,調來調去的,剛開始肯定會看暈。
但是當你熟悉了MVP架構,并掌握了它的精髓后,會發現雖然增加了很多代碼,但是整體架構變得非常清晰,代碼也可以多處復用。各個類和層的職責都非常明確且單一,后期的擴展,維護都會更加容易。整體的可測試性非常的好,UI層和業務層可以分別進行單元測試。
項目代碼已共享到Github:AndroidMVPArchitecture
六、效果圖
六、參考資料
PS:歡迎關注SherlockShi博客