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

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

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

該系列文章會不斷更新Android項(xiàng)目開發(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)的實(shí)際應(yīng)用

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

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

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

MVP

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

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

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

2.1 Presenter生命周期的問題

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

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

2.2 跨模塊調(diào)用

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

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

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

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

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

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

new MVP

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

分析各個(gè)層:

3.1 View層

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

3.2 Presenter層

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

3.3 Interactor層

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

4 一個(gè)代碼示例

下面簡述一個(gè)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頁面點(diǎn)擊發(fā)帖,需要判斷當(dāng)前登錄狀態(tài)。則在HomePresenter中同時(shí)持有LoginInteractor的實(shí)例。

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項(xiàng)目:BaseAndroidProject

5 結(jié)尾

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

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


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

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

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