Android架構系列-MVP架構的實際應用

Tsy遠發表於2019-03-01

本文簡述了在實際專案中使用MVP架構遇到的問題和相應處理,最終整理出升級版的MVP架構。

0 Android架構系列文章

該系列文章會不斷更新Android專案開發中一些好的架構和小技巧

系列一 Android架構系列-基於MVP建立適合自己的架構
系列二 Android架構系列-如何優美的寫Intent
系列三 Android架構系列-開發規範
系列四 Android架構系列-封裝自己的okhttp
系列五 Android架構系列-MVP架構的實際應用

1 原有的MVP架構

在系列文章的第一篇文章中介紹了使用MVP架構。詳細可以回看該文章

MVP的結構如下圖:

MVP
MVP

2 實際專案中應用出現的問題

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

但是真實開發中發現,MVP是一種模組中高內聚的模式,Presenter層接管了Activity中的邏輯實現。相應出現了以下幾個問題:

2.1 Presenter生命週期的問題

Presnter層和View層是一一對應的,所以Presnter層和View層生命週期是一致的。

但是現在所有邏輯寫在Presenter層中,如果其他地方需要呼叫就只能通過靜態方法呼叫,不能再次new 一個 Presenter例項

2.2 跨模組呼叫

實際開發中經常會有在B模組呼叫A模組的部分邏輯。

比如發帖時要判斷使用者是否登入,並且獲取當前登入使用者資訊。即在發帖模組要獲取使用者模組的資料和邏輯。

如果邏輯寫在Presenter中,則其他模組只能直接讀取當前使用者快取,然後在自己模組解析。還是增加了模組間的耦合。

3 優化的MVP分層

在這裡將Model層命名為Interactor。我們將每個模組內部的原子邏輯(一個功能而不是一系列邏輯功能)都寫在interactor中,Presenter層只負責接收view事件,呼叫interactor功能,再回饋view。

在此,一個Presenter可以持有多個模組的Interactor,這樣就可以訪問相應功能邏輯和資料。並且不需要在自己模組對其他模組資料進行解析處理。

new MVP
new MVP

該優化後的分層和普通的MVP最大的區別在於,將Presenter層解放出來,裡面不再放具體邏輯,直接呼叫邏輯。

分析各個層:

3.1 View層

  1. 只持有和自己一一對應的Presenter例項,通過實現介面方式呼叫
  2. 負責頁面的控制元件初始化, 重新整理顯示頁面, 監聽元素事件
  3. 不應該出現狀態, 邏輯等程式碼(除非只跟頁面相關的很小的邏輯,比如一個欄位標識密碼是否可見)

3.2 Presenter層

  1. 持有和自己一一對應的View例項,可以持有多個模組的Interactor層。通過實現介面方式呼叫。
  2. 作為View和Interactor層的Glue層, 接收view操作, 呼叫模組中方法, 返回資料給view。

3.3 Interactor層

  1. 本模組中原子性邏輯封裝,非一個系列的邏輯,這樣保證其他地方可以方便的呼叫。
  2. Interactor層中不應該出現其他模組的引用
  3. Interactor層的返回。如果是同步直接返回資料, 如果是非同步在contract.interactor中定義callback。

4 一個程式碼示例

下面簡述一個Sample 登入程式碼

LoginContract層:

public interface LoginContract {

    interface View extends BaseView {
        /**
         * 跳轉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();

        /**
         * 獲取當前登入使用者
         * @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() {
        //跳轉Home頁面
        ToastUtils.showShort(GlobalApp.getInstance().getContext(), "登入成功, 跳轉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) {
        //模擬非同步網路請求登入

        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層。下面展示其他模組如何呼叫Login模組的邏輯或者資料。

假如Home頁面點選發帖,需要判斷當前登入狀態。則在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()) {
            // 跳轉登入
            // TODO: 16/8/30
            return;
        }

        //跳轉發帖頁面
        // TODO: 16/8/30
    }
}複製程式碼

具體的程式碼在Github專案:BaseAndroidProject

5 結尾

無論是MVP還是什麼架構,最終的目的都是寫出易讀性和測試性強的程式碼。所以不要對於架構鑽牛角尖,過度設計不可取。在實際開發中架構自然會跟著升級。謹記!均衡合理!!

更多文章關注我的公眾號

我的公眾號
我的公眾號

相關文章