Android架構(gòu)設(shè)計(jì)---MVP模式第(一)篇之基本認(rèn)實(shí)

版權(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)容還是比較多的,但是不難,一步一步的看吧!

項(xiàng)目結(jié)構(gòu)劃分

如果要實(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ò)誤的地方,還望指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容