Android架構(gòu)系列-MVP架構(gòu)的實際應(yīng)用

本文簡述了在實際項目中使用MVP架構(gòu)遇到的問題和相應(yīng)處理,最終整理出升級版的MVP架構(gòu)寂曹。

0 Android架構(gòu)系列文章

該系列文章會不斷更新Android項目開發(fā)中一些好的架構(gòu)和小技巧

系列一 Android架構(gòu)系列-基于MVP創(chuàng)建適合自己的架構(gòu)
系列二 Android架構(gòu)系列-如何優(yōu)美的寫Intent
系列三 Android架構(gòu)系列-開發(fā)規(guī)范
系列四 Android架構(gòu)系列-封裝自己的okhttp
系列五 Android架構(gòu)系列-MVP架構(gòu)的實際應(yīng)用

1 原有的MVP架構(gòu)

在系列文章的第一篇文章中介紹了使用MVP架構(gòu)腹备。詳細(xì)可以回看該文章

MVP的結(jié)構(gòu)如下圖:

MVP

2 實際項目中應(yīng)用出現(xiàn)的問題

MVP是一種代碼的分層思想衬潦,其實沒有用到任何庫,只是告訴了你如何規(guī)整的放置代碼植酥。使各個層次的代碼各司其職别渔,增加易讀性和可測試性。

但是真實開發(fā)中發(fā)現(xiàn)惧互,MVP是一種模塊中高內(nèi)聚的模式,Presenter層接管了Activity中的邏輯實現(xiàn)喇伯。相應(yīng)出現(xiàn)了以下幾個問題:

2.1 Presenter生命周期的問題

Presnter層和View層是一一對應(yīng)的喊儡,所以Presnter層和View層生命周期是一致的。

但是現(xiàn)在所有邏輯寫在Presenter層中稻据,如果其他地方需要調(diào)用就只能通過靜態(tài)方法調(diào)用艾猜,不能再次new 一個 Presenter實例

2.2 跨模塊調(diào)用

實際開發(fā)中經(jīng)常會有在B模塊調(diào)用A模塊的部分邏輯买喧。

比如發(fā)帖時要判斷用戶是否登錄,并且獲取當(dāng)前登錄用戶信息匆赃。即在發(fā)帖模塊要獲取用戶模塊的數(shù)據(jù)和邏輯淤毛。

如果邏輯寫在Presenter中,則其他模塊只能直接讀取當(dāng)前用戶緩存算柳,然后在自己模塊解析低淡。還是增加了模塊間的耦合。

3 優(yōu)化的MVP分層

在這里將Model層命名為Interactor瞬项。我們將每個模塊內(nèi)部的原子邏輯(一個功能而不是一系列邏輯功能)都寫在interactor中蔗蹋,Presenter層只負(fù)責(zé)接收view事件,調(diào)用interactor功能囱淋,再回饋view猪杭。

在此,一個Presenter可以持有多個模塊的Interactor妥衣,這樣就可以訪問相應(yīng)功能邏輯和數(shù)據(jù)皂吮。并且不需要在自己模塊對其他模塊數(shù)據(jù)進(jìn)行解析處理。

new MVP

該優(yōu)化后的分層和普通的MVP最大的區(qū)別在于税手,將Presenter層解放出來蜂筹,里面不再放具體邏輯,直接調(diào)用邏輯冈止。

分析各個層:

3.1 View層

  1. 只持有和自己一一對應(yīng)的Presenter實例狂票,通過實現(xiàn)接口方式調(diào)用
  2. 負(fù)責(zé)頁面的控件初始化, 刷新顯示頁面, 監(jiān)聽元素事件
  3. 不應(yīng)該出現(xiàn)狀態(tài), 邏輯等代碼(除非只跟頁面相關(guān)的很小的邏輯,比如一個字段標(biāo)識密碼是否可見)

3.2 Presenter層

  1. 持有和自己一一對應(yīng)的View實例熙暴,可以持有多個模塊的Interactor層闺属。通過實現(xiàn)接口方式調(diào)用。
  2. 作為View和Interactor層的Glue層, 接收view操作, 調(diào)用模塊中方法, 返回數(shù)據(jù)給view周霉。

3.3 Interactor層

  1. 本模塊中原子性邏輯封裝掂器,非一個系列的邏輯,這樣保證其他地方可以方便的調(diào)用俱箱。
  2. Interactor層中不應(yīng)該出現(xiàn)其他模塊的引用
  3. Interactor層的返回国瓮。如果是同步直接返回數(shù)據(jù), 如果是異步在contract.interactor中定義callback。

4 一個代碼示例

下面簡述一個Sample 登錄代碼

LoginContract層:

public interface LoginContract {

    interface View extends BaseView {
        /**
         * 跳轉(zhuǎn)Home
         */
        void goHome();
    }

    interface Presenter extends BasePresenter {
        /**
         * login
         * @param phone
         * @param password
         */
        void onLogin(String phone, String password);
    }

    interface Interactor {
        /**
         * do login
         * @param phone
         * @param password
         * @param callback
         */
        void doLogin(String phone, String password, LoginCallback callback);
        interface LoginCallback {
            void onSuccess(UserInfo user_info);
            void onFailure(String msg);
        }

        /**
         * 是否登錄
         * @return
         */
        boolean isLogin();

        /**
         * 獲取當(dāng)前登錄用戶
         * @return
         */
        UserInfo getLoginUser();
    }
}

LoginActivity:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        ButterKnife.bind(this);

        mPresenter = new LoginPresenter(this);
    }

    @OnClick(R.id.btnLogin)
    public void onLogin() {
        mPresenter.onLogin(editPhone.getText().toString(), editPassword.getText().toString());
    }

    @OnClick(R.id.txtRegister)
    public void goRegister() {
        ToastUtils.showShort(GlobalApp.getInstance().getContext(), "goRegister");
    }

    @OnClick(R.id.txtForgetPwd)
    public void goForgetPwd() {
        ToastUtils.showShort(GlobalApp.getInstance().getContext(), "goForgetPwd");
    }

    @Override
    public void showToast(String msg) {
        ToastUtils.showShort(GlobalApp.getInstance().getContext(), msg);
    }

    @Override
    public void goHome() {
        //跳轉(zhuǎn)Home頁面
        ToastUtils.showShort(GlobalApp.getInstance().getContext(), "登錄成功, 跳轉(zhuǎn)Home頁面");

        Intent intent = new Intent(this, HomeActivity.class);
        startActivity(intent);
        finish();
    }

LoginPresnter:

public class LoginPresenter implements LoginContract.Presenter {

    private LoginContract.View mView;
    private LoginContract.Interactor mInteractor;

    public LoginPresenter(LoginContract.View view) {
        mView = view;
        mInteractor = new LoginInteractor();
    }

    @Override
    public void start() {

    }

    @Override
    public void onLogin(String phone, String password) {
        if(StringUtils.isEmpty(phone)) {
            mView.showToast("Empty phone");
            return;
        }

        if(StringUtils.isEmpty(password)) {
            mView.showToast("Empty password");
            return;
        }

        mInteractor.doLogin(phone, password, new LoginContract.Interactor.LoginCallback() {
            @Override
            public void onSuccess(UserInfo user_info) {
                mView.goHome();
            }

            @Override
            public void onFailure(String msg) {
                mView.showToast(msg);
            }
        });
    }
}

LoginInteractor:

public class LoginInteractor implements LoginContract.Interactor {

    private MyOkHttp mApi;
    private ACache mCache;

    //緩存key
    private final String CACHE_KEY_USERINFO = "CACHE_KEY_USERINFO";

    public LoginInteractor() {
        mApi = MyOkHttp.get();
        mCache = ACache.get(GlobalApp.getInstance().getContext());
    }

    @Override
    public void doLogin(String phone, String password, final LoginCallback callback) {
        //模擬異步網(wǎng)絡(luò)請求登錄

        Handler handler = new Handler();

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                UserInfo userInfo = new UserInfo();
                userInfo.uid = "1212121";
                userInfo.userName = "tsy12321";
                userInfo.token = "wqw13w12312wsqw12";

                //存入緩存
                mCache.put(CACHE_KEY_USERINFO, userInfo);

                callback.onSuccess(userInfo);
            }
        }, 2000);
    }

    @Override
    public boolean isLogin() {
        UserInfo userInfo = (UserInfo) mCache.getAsObject(CACHE_KEY_USERINFO);
        if(!StringUtils.isEmpty(userInfo.uid) && !StringUtils.isEmpty(userInfo.token)) {
            return true;
        }
        return false;
    }

    @Override
    public UserInfo getLoginUser() {
        return (UserInfo) mCache.getAsObject(CACHE_KEY_USERINFO);
    }
}

由以上示例可以看出狞谱,具體的邏輯都放在了Interactor層乃摹。下面展示其他模塊如何調(diào)用Login模塊的邏輯或者數(shù)據(jù)。

假如Home頁面點擊發(fā)帖跟衅,需要判斷當(dāng)前登錄狀態(tài)孵睬。則在HomePresenter中同時持有LoginInteractor的實例。

public class HomePresenter implements HomeContract.Presenter {

    private HomeContract.View mView;
    private HomeContract.Interactor mInteractor;
    private LoginContract.Interactor mLoginInteractor;

    public HomePresenter(HomeContract.View view) {
        mView = view;
        mInteractor = new HomeInteractor();
        mLoginInteractor = new LoginInteractor();
    }

    @Override
    public void start() {

    }

    @Override
    public void onPost() {
        //判斷用戶有沒有登錄
        if(!mLoginInteractor.isLogin()) {
            // 跳轉(zhuǎn)登錄
            // TODO: 16/8/30
            return;
        }

        //跳轉(zhuǎn)發(fā)帖頁面
        // TODO: 16/8/30
    }
}

具體的代碼在Github項目:BaseAndroidProject

5 結(jié)尾

無論是MVP還是什么架構(gòu)伶跷,最終的目的都是寫出易讀性和測試性強(qiáng)的代碼掰读。所以不要對于架構(gòu)鉆牛角尖秘狞,過度設(shè)計不可取。在實際開發(fā)中架構(gòu)自然會跟著升級蹈集。謹(jǐn)記烁试!均衡合理!拢肆!

更多文章關(guān)注我的公眾號


我的公眾號
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末减响,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子善榛,更是在濱河造成了極大的恐慌辩蛋,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件移盆,死亡現(xiàn)場離奇詭異悼院,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咒循,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門据途,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人叙甸,你說我怎么就攤上這事颖医。” “怎么了裆蒸?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵熔萧,是天一觀的道長。 經(jīng)常有香客問我僚祷,道長佛致,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任辙谜,我火速辦了婚禮俺榆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘装哆。我一直安慰自己罐脊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布蜕琴。 她就那樣靜靜地躺著萍桌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凌简。 梳的紋絲不亂的頭發(fā)上上炎,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音号醉,去河邊找鬼反症。 笑死,一個胖子當(dāng)著我的面吹牛畔派,可吹牛的內(nèi)容都是我干的铅碍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼线椰,長吁一口氣:“原來是場噩夢啊……” “哼胞谈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起憨愉,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤烦绳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后配紫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體径密,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年躺孝,在試婚紗的時候發(fā)現(xiàn)自己被綠了享扔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡植袍,死狀恐怖惧眠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情于个,我是刑警寧澤氛魁,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站厅篓,受9級特大地震影響秀存,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贷笛,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一应又、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乏苦,春花似錦株扛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掀淘,卻和暖如春旬蟋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背革娄。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工倾贰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留冕碟,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓匆浙,卻偏偏與公主長得像安寺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子首尼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

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