版權聲明:本文為LooperJing原創文章,轉載請注明出處!
MVP 這種模式出現已經很久了,在網上有些關于 MVP 開源代碼2014年就有了,近期有關注項目架構方面的內容,于是乎,作為一個還不懂什么是 MVP 的人,那么就一定要了解一下的。網上關于 MVP 的資料其實也不少,通常都要把 MVP 和 MVC 做一下比較,我喜歡直接了當,相信有耐心看MVP的人是一定懂 MVC 的,MVC 的略過。本文的項目地址是:https://github.com/herojing/JokeMVP,下面結合項目談談MVP是個什么東西,以下就當作自己的學習總結筆記吧。
一、什么是MVP?
MVP 是 Model、Presenter、View 的縮寫,三個部分的關系如下圖所示。
在 Android 項目中,負責界面展示的模塊(所有的 Activitiy 、Fragment以及 View 的子類)都可以劃分到 View 這個層次,所有的業務邏輯處理(請求網絡數據、數據庫讀取等)可以劃分到 Model 這個層次,為了使得 View 和 Model 之間松耦合,用 Presenter 幫助解耦。所以可以猜測,在具體實現中 Presenter 類肯定要持有 View 和 Model 的引用。現在來說一下,上圖中三個箭頭的意思。流程是這樣子的,從左到右看,比如我們剛進入一個 Activity,那么這個 Activity 做為 View 層,肯定需要通知 Presenter 加載數據,而Presenter會繼續調用Model層加載數據,等Model加載完畢后,回調給 Presenter,Presenter 持有View引用,再通知View更新界面。
二、MVP的效果
采用MVP的目的就是使得層次更加清晰,業務邏輯與 UI 分離,那么采用 MVP 以后的效果如何呢?DEMO 實現的是一個列表,效果如圖下圖所示,列表的內容是一些笑話信息。
如果上面的頁面采用 MVP 的模式進行設計的話,那么Activity中的代碼將非常清潔!請看下面。
public class MainActivity extends BaseActivity implements JokeView {
// 不做分頁加載的操作,所以這兩個參數寫死
public static final String PAGE_NUM = "1";
public static final String PAGE_SIZE = "20";
private ListView mListView;
private JokePresenter mJokePresenter = null;
private ArrayList<JokeInfo> mJokeInfoArrayList = null;
private JokeAdapter mJokeAdapter;
@Override
public void initVariables() {
mJokeInfoArrayList = new ArrayList<>();
mJokePresenter = new JokePresenterImpl(this);
}
@Override
public void initView() {
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.main_page_joke_lv);
}
@Override
public void loaderData() {
mJokeAdapter = new JokeAdapter(this, mJokeInfoArrayList);
mListView.setAdapter(mJokeAdapter);
//通知 Presenter 加載數據
mJokePresenter.getJoke(PAGE_NUM, PAGE_SIZE);
}
@Override
public void showLoading() {
// TODO 顯示進度條
}
@Override
public void hideLoading() {
// TODO 隱藏進度條
}
@Override
public void setJoke(Joke pJoke) {
if (pJoke != null) {
Joke.Result result = pJoke.getResult();
if (result != null) {
ArrayList<JokeInfo> jokeInfoArrayList = result.getJokeInfoArrayList();
mJokeInfoArrayList.addAll(jokeInfoArrayList);
mJokeAdapter.notifyDataSetChanged();
}
}
}
@Override
public void showError() {
TextView errorView = new TextView(this);
errorView.setTextSize(20);
errorView.setText("請求失敗了");
mListView.setEmptyView(errorView);
}
}
我重新定義了一下 Activity的“生命周期”,這個 MainActivity 繼承了 BaseActivity ,BaseActivity 的實現如下:
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initVariables();
initView();
loaderData();
}
/**
* 做初始化方面的工作,比如接收上一個界面的Intent
*/
public abstract void initVariables();
/**
* 初始化控件
*/
public abstract void initView();
/**
* 加載數據
*/
public abstract void loaderData();
}
如果你覺的還不錯,那么可以繼續看下面了,下面將具體闡述 MVP 三個部分是如何協同操作的。
三、View層實現
在講述View層實現之前,首先看一下,項目的整體結構劃分,有個大致的感覺。如下圖所示。感覺內容還是比較多的,但是不難,一步一步的看吧!
如果要實現上面的效果,首先做一下需求分析,每一條的笑話實體類包括的屬性有笑話內容、時間;所以建立一個 Joke 實體類是很簡單的。View層承擔著界面的更新,MVP 中一般將界面更新的職責都交給一個 XXView ,我們的項目姑且叫做 JokeView 。當 Model 層請求到數據的時候,通知 Presenter 層后,Presenter 層就會調用 JokeView 進行界面的更新,所以需要一個設置笑話的方法;請求會有加載時間,所以界面要顯示 Loading ,請求結束后需要隱藏 Loading ;當斷網等異常情況發生的時候,還需要提醒用戶請求發生了錯誤,所以還需要顯示錯誤界面的方法。綜上,定義的 JokeView 接口如下;定義好 JokeView 后,就可以讓 Activity 實現 JokeView 接口,重寫里面的方法進行更新了。所以我覺得在 MVP 模式開發的過程中,最先確定的就是寫一個 XXView。
public interface JokeView {
void showLoading();
void hideLoading();
void setJoke(Joke pJoke);
void showError();
}
四、Model 層實現
在 Model 層中做的主要的工作就是請求網絡數據了。請求邏輯我用了 Volley ,具體可以看項目中是如何實現的,也是參考了網上一個開源代碼,具體的地址記不清了 。
public class JokeModelImpl implements JokeModel {
public static final String REQUEST_SERVER_URL="http://api.jisuapi.com/xiaohua/text?";
public static final String APPKEY="&appkey=9814b57c706d0a23";
//http://api.jisuapi.com/xiaohua/text?pagenum=10&pagesize=3&appkey=9814b57c706d0a23
@Override
public void getJoke(String pNum, String pSize, final OnJokeListener pOnJokeListener) {
VolleyRequest.newInstance().newGsonRequest(REQUEST_SERVER_URL+"pagenum="+pNum+"&"+"pagesize="+pSize+"&sort=addtime"+APPKEY,
Joke.class, new Response.Listener<Joke>() {
@Override
public void onResponse(Joke pJoke) {
if (pJoke != null) {
pOnJokeListener.onSuccess(pJoke);
} else {
pOnJokeListener.onError();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
pOnJokeListener.onError();
}
});
}
}
其中 OnJokeListener 是 Presenter 層中定義的接口,用與通知 Presenter 層要調用 View 層更新數據。
public interface OnJokeListener {
/**
* 成功的時候回調
* @param pJoke joke
*/
void onSuccess(Joke pJoke);
/**
* 失敗的時候回調
*/
void onError();
}
五、Presenter 層實現
在 Model 層和 View 層都定義好了之后,就可以寫 Presenter 層了,之前已經多次說過 Presenter 層作為 Model 和 View 的橋梁,需要持有 Model 和 View 的引用。Presenter 需要實現 OnJokeListener 接口,具體的實現如下:
public class JokePresenterImpl implements JokePresenter, OnJokeListener {
// P層作為M層和V層的銜接者,需要持有JokeView和JokeModel的引用
private JokeModel mJokeModel = new JokeModelImpl();
private JokeView mJokeView;
public JokePresenterImpl(JokeView jokeView) {
mJokeView = jokeView;
}
/**
* 調用M層取數據,getJoke由所展示的界面(Activity)調用
*
* @param pNum
* @param pSize
*/
@Override
public void getJoke(String pNum, String pSize) {
mJokeView.showLoading();
mJokeModel.getJoke(pNum, pSize, this);
}
/**
* 接收M層的回調,調用View 層進行界面的刷新
*
*/
@Override
public void onSuccess(Joke pJoke) {
mJokeView.setJoke(pJoke);
}
@Override
public void onError() {
mJokeView.showError();
}
}
六、總結
最后重新梳理一下 MVP 的編寫方式。
1、 根據項目需求,寫一個 XXView 接口。然后讓對應的 Activity/Fragment 實現這個接口。View 層基本搞定!
2、編寫 Model 層,主要就是網絡數據請求了或者其他什么耗時操作,實現方式盡情發揮你的想象,但是最后一定需要用 Presenter 層定義的接口,回調給 Presenter 通知 View 層 更新數據。
3、編寫 Presenter 層,Presenter 層需要持有 View 層和 Model層的引用,并且實現 Presenter 層定義的回調接口。在回調接口中調用 View 層的代碼 進行界面更新,最重要的是,有一個調用通過Model層的方法,在此方法中,調用 Model 層請求數據。
4、回到View 層的Activity ,調用 Presenter 層獲取數據。到此完成。
備注:為了遵守面向接口編程的原則,做了一下接口的抽取。如Presenter 中 實現了 JokePresenter 接口,Model 層中實現了 JokeModel 接口。好了,如果在閱讀中,發現了有錯誤的地方,還望指正。