版權(quán)聲明:本文為LooperJing原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處!
MVP 這種模式出現(xiàn)已經(jīng)很久了,在網(wǎng)上有些關(guān)于 MVP 開源代碼2014年就有了,近期有關(guān)注項(xiàng)目架構(gòu)方面的內(nèi)容,于是乎,作為一個(gè)還不懂什么是 MVP 的人,那么就一定要了解一下的。網(wǎng)上關(guān)于 MVP 的資料其實(shí)也不少,通常都要把 MVP 和 MVC 做一下比較,我喜歡直接了當(dāng),相信有耐心看MVP的人是一定懂 MVC 的,MVC 的略過。本文的項(xiàng)目地址是:https://github.com/herojing/JokeMVP,下面結(jié)合項(xiàng)目談?wù)凪VP是個(gè)什么東西,以下就當(dāng)作自己的學(xué)習(xí)總結(jié)筆記吧。
一、什么是MVP?
MVP 是 Model、Presenter、View 的縮寫,三個(gè)部分的關(guān)系如下圖所示。

在 Android 項(xiàng)目中,負(fù)責(zé)界面展示的模塊(所有的 Activitiy 、Fragment以及 View 的子類)都可以劃分到 View 這個(gè)層次,所有的業(yè)務(wù)邏輯處理(請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)、數(shù)據(jù)庫(kù)讀取等)可以劃分到 Model 這個(gè)層次,為了使得 View 和 Model 之間松耦合,用 Presenter 幫助解耦。所以可以猜測(cè),在具體實(shí)現(xiàn)中 Presenter 類肯定要持有 View 和 Model 的引用?,F(xiàn)在來說一下,上圖中三個(gè)箭頭的意思。流程是這樣子的,從左到右看,比如我們剛進(jìn)入一個(gè) Activity,那么這個(gè) Activity 做為 View 層,肯定需要通知 Presenter 加載數(shù)據(jù),而Presenter會(huì)繼續(xù)調(diào)用Model層加載數(shù)據(jù),等Model加載完畢后,回調(diào)給 Presenter,Presenter 持有View引用,再通知View更新界面。
二、MVP的效果
采用MVP的目的就是使得層次更加清晰,業(yè)務(wù)邏輯與 UI 分離,那么采用 MVP 以后的效果如何呢?DEMO 實(shí)現(xiàn)的是一個(gè)列表,效果如圖下圖所示,列表的內(nèi)容是一些笑話信息。

如果上面的頁(yè)面采用 MVP 的模式進(jìn)行設(shè)計(jì)的話,那么Activity中的代碼將非常清潔!請(qǐng)看下面。
public class MainActivity extends BaseActivity implements JokeView {
// 不做分頁(yè)加載的操作,所以這兩個(gè)參數(shù)寫死
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 加載數(shù)據(jù)
mJokePresenter.getJoke(PAGE_NUM, PAGE_SIZE);
}
@Override
public void showLoading() {
// TODO 顯示進(jìn)度條
}
@Override
public void hideLoading() {
// TODO 隱藏進(jìn)度條
}
@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("請(qǐng)求失敗了");
mListView.setEmptyView(errorView);
}
}
我重新定義了一下 Activity的“生命周期”,這個(gè) MainActivity 繼承了 BaseActivity ,BaseActivity 的實(shí)現(xiàn)如下:
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initVariables();
initView();
loaderData();
}
/**
* 做初始化方面的工作,比如接收上一個(gè)界面的Intent
*/
public abstract void initVariables();
/**
* 初始化控件
*/
public abstract void initView();
/**
* 加載數(shù)據(jù)
*/
public abstract void loaderData();
}
如果你覺的還不錯(cuò),那么可以繼續(xù)看下面了,下面將具體闡述 MVP 三個(gè)部分是如何協(xié)同操作的。
三、View層實(shí)現(xiàn)
在講述View層實(shí)現(xiàn)之前,首先看一下,項(xiàng)目的整體結(jié)構(gòu)劃分,有個(gè)大致的感覺。如下圖所示。感覺內(nèi)容還是比較多的,但是不難,一步一步的看吧!

如果要實(shí)現(xiàn)上面的效果,首先做一下需求分析,每一條的笑話實(shí)體類包括的屬性有笑話內(nèi)容、時(shí)間;所以建立一個(gè) Joke 實(shí)體類是很簡(jiǎn)單的。View層承擔(dān)著界面的更新,MVP 中一般將界面更新的職責(zé)都交給一個(gè) XXView ,我們的項(xiàng)目姑且叫做 JokeView 。當(dāng) Model 層請(qǐng)求到數(shù)據(jù)的時(shí)候,通知 Presenter 層后,Presenter 層就會(huì)調(diào)用 JokeView 進(jìn)行界面的更新,所以需要一個(gè)設(shè)置笑話的方法;請(qǐng)求會(huì)有加載時(shí)間,所以界面要顯示 Loading ,請(qǐng)求結(jié)束后需要隱藏 Loading ;當(dāng)斷網(wǎng)等異常情況發(fā)生的時(shí)候,還需要提醒用戶請(qǐng)求發(fā)生了錯(cuò)誤,所以還需要顯示錯(cuò)誤界面的方法。綜上,定義的 JokeView 接口如下;定義好 JokeView 后,就可以讓 Activity 實(shí)現(xiàn) JokeView 接口,重寫里面的方法進(jìn)行更新了。所以我覺得在 MVP 模式開發(fā)的過程中,最先確定的就是寫一個(gè) XXView。
public interface JokeView {
void showLoading();
void hideLoading();
void setJoke(Joke pJoke);
void showError();
}
四、Model 層實(shí)現(xiàn)
在 Model 層中做的主要的工作就是請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)了。請(qǐng)求邏輯我用了 Volley ,具體可以看項(xiàng)目中是如何實(shí)現(xiàn)的,也是參考了網(wǎng)上一個(gè)開源代碼,具體的地址記不清了 。
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 層要調(diào)用 View 層更新數(shù)據(jù)。
public interface OnJokeListener {
/**
* 成功的時(shí)候回調(diào)
* @param pJoke joke
*/
void onSuccess(Joke pJoke);
/**
* 失敗的時(shí)候回調(diào)
*/
void onError();
}
五、Presenter 層實(shí)現(xiàn)
在 Model 層和 View 層都定義好了之后,就可以寫 Presenter 層了,之前已經(jīng)多次說過 Presenter 層作為 Model 和 View 的橋梁,需要持有 Model 和 View 的引用。Presenter 需要實(shí)現(xiàn) OnJokeListener 接口,具體的實(shí)現(xiàn)如下:
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;
}
/**
* 調(diào)用M層取數(shù)據(jù),getJoke由所展示的界面(Activity)調(diào)用
*
* @param pNum
* @param pSize
*/
@Override
public void getJoke(String pNum, String pSize) {
mJokeView.showLoading();
mJokeModel.getJoke(pNum, pSize, this);
}
/**
* 接收M層的回調(diào),調(diào)用View 層進(jìn)行界面的刷新
*
*/
@Override
public void onSuccess(Joke pJoke) {
mJokeView.setJoke(pJoke);
}
@Override
public void onError() {
mJokeView.showError();
}
}
六、總結(jié)
最后重新梳理一下 MVP 的編寫方式。
1、 根據(jù)項(xiàng)目需求,寫一個(gè) XXView 接口。然后讓對(duì)應(yīng)的 Activity/Fragment 實(shí)現(xiàn)這個(gè)接口。View 層基本搞定!
2、編寫 Model 層,主要就是網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求了或者其他什么耗時(shí)操作,實(shí)現(xiàn)方式盡情發(fā)揮你的想象,但是最后一定需要用 Presenter 層定義的接口,回調(diào)給 Presenter 通知 View 層 更新數(shù)據(jù)。
3、編寫 Presenter 層,Presenter 層需要持有 View 層和 Model層的引用,并且實(shí)現(xiàn) Presenter 層定義的回調(diào)接口。在回調(diào)接口中調(diào)用 View 層的代碼 進(jìn)行界面更新,最重要的是,有一個(gè)調(diào)用通過Model層的方法,在此方法中,調(diào)用 Model 層請(qǐng)求數(shù)據(jù)。
4、回到View 層的Activity ,調(diào)用 Presenter 層獲取數(shù)據(jù)。到此完成。
備注:為了遵守面向接口編程的原則,做了一下接口的抽取。如Presenter 中 實(shí)現(xiàn)了 JokePresenter 接口,Model 層中實(shí)現(xiàn)了 JokeModel 接口。好了,如果在閱讀中,發(fā)現(xiàn)了有錯(cuò)誤的地方,還望指正。