Android MVP架構(gòu)實(shí)踐

一补憾、前言

首先聲明一下叛拷,沒有完美的架構(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)圖

MVP架構(gòu)圖

(上圖由ProcessOn在線工具繪制)

2. 類圖


(上圖由StarUML繪制)

四、MVP實(shí)踐

1. 兩個(gè)基類接口

首先定義兩個(gè)接口娩缰,這兩個(gè)接口分別是所有View和Presenter的基類: IBaseViewIBasePresenter灸撰。

  • 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 詳解(下)

Android MVP 實(shí)戰(zhàn)經(jīng)驗(yàn)

PS:歡迎關(guān)注SherlockShi博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市轩性,隨后出現(xiàn)的幾起案子声登,更是在濱河造成了極大的恐慌,老刑警劉巖揣苏,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悯嗓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡卸察,警方通過查閱死者的電腦和手機(jī)脯厨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坑质,“玉大人合武,你說我怎么就攤上這事∥卸螅” “怎么了稼跳?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吃沪。 經(jīng)常有香客問我汤善,道長(zhǎng),這世上最難降的妖魔是什么票彪? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任萎津,我火速辦了婚禮,結(jié)果婚禮上抹镊,老公的妹妹穿的比我還像新娘锉屈。我一直安慰自己,他們只是感情好垮耳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布颈渊。 她就那樣靜靜地躺著遂黍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俊嗽。 梳的紋絲不亂的頭發(fā)上雾家,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音绍豁,去河邊找鬼芯咧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛竹揍,可吹牛的內(nèi)容都是我干的敬飒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼芬位,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼无拗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起昧碉,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤英染,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后被饿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體四康,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年狭握,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闪金。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哥牍,死狀恐怖毕泌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嗅辣,我是刑警寧澤撼泛,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站澡谭,受9級(jí)特大地震影響愿题,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛙奖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一潘酗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雁仲,春花似錦仔夺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)日裙。三九已至,卻和暖如春惰蜜,著一層夾襖步出監(jiān)牢的瞬間昂拂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工抛猖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留格侯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓财著,卻偏偏與公主長(zhǎng)得像联四,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瓢宦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容