Android學習筆記之MVP框架模式

前言

在學習了 MVC 架構之后,發現 Activity 和 Fragment 和 XML 界面的開發就是典型的 MVC 架構模式,在 Activity 中不僅要處理 UI 操作,還要處理請求數據的操作。

然而在我接觸到的開發項目中,看到一個 Activity 中的代碼行數接近上千行是常態。在修改的過程中經常要在類里面不斷的翻上翻下的,修改起來也非常費勁。

這樣就導致了 MVC 架構模式耦合度太高、職責不明確,不易于維護的原因,這次就來學習 MVC 的演變出來的 MVP 架構模式。

MVC 的工作原理: MVC 即 Model View Controller,簡單來說就是通過 Controller 的控制去操作 Model 層的數據,并且返回給 View 層展示。當用戶觸發事件的時候,View 層會發送指令到 Controller 層,接著 Controller 去通知 Model 層更新數據,Model 層更新完數據以后直接顯示在 View 層上,這就是 MVC 的工作原理。

MVP是什么

MVP 的全稱是 Model-View-Presenter,MVP 是 MVC 的一種演進版本,將 MVC 中的 Controller 改為了 Presenter,View 通過接口與 Presenter 進行交互,有效降低 View(Activity / Fragment) 的復雜性,避免業務邏輯被塞進 View 中,使得 View 變得臃腫。

另外,MVP 模式會解除 View 與 Model 的耦合,同時又帶來了良好的可擴展性、可測試性,保證了系統的整潔性、靈活性。雖然在簡單的應用中可能會因為各種接口變得復雜,但在稍有規模的應用中,依然能保持結構的整潔和靈活。

MVP的結構

  • Model 主要是提供數據的存取功能,Presenter 需要通過 Model 層存儲和獲取數據,Model 就像一個數據倉庫。Model 是管理數據庫和網絡獲取數據的角色。

  • View 一般是指 Activity 和 Fragmen t等,它含有一個 Presenter 成員變量。通常 View 需要實現一個邏輯接口,將 View 上的操作轉交給 Presenter 進行實現,最后 Presenter 調用 View 的邏輯接口將結果返回給 View 元素。

  • Presenter 作為 View 與 Model 交互的中間紐帶,處理與用戶交互的負責邏輯。它從 Model 層檢索數據后,返回給 View 層,使得 View 和 Model 之間沒有耦合,也將業務邏輯從View角色上抽離出來。

mvp.jpg

在通常開發中,View是指 Activity / Fragment,不過,Presenter 和 Activity 通過定義一個 view 接口進行關聯,而 Presenter 和 Model 是通過 Callback 接口進行關聯。

  • View接口:顯示提示框、數據更新;
  • Callback接口:請求數據時反饋狀態(成功、失敗和異常等等);
mvp.png

MVP的優缺點

優點

  1. 分離了視圖邏輯和業務邏輯,降低了耦合。

  2. Activity 只處理生命周期的任務,代碼變得更加簡潔。

  3. 視圖邏輯和業務邏輯分別抽象到了 View 和 Presenter 的接口中去,提高代碼的可閱讀性。

  4. Presenter 被抽象成接口,可以有多種具體的實現,所以方便進行單元測試

  5. 把業務邏輯抽到 Presenter 中去,避免后臺線程引用著 Activity 導致 Activity 的資源無法被系統回收從而引起內存泄露。

  6. Presenter 代碼可復用,一個 Presenter 可以用于多個 View,而不需要更改 Presenter 的邏輯。

缺點

  1. Presenter 中除了應用邏輯以外,還有大量的 View->Model,Model->View 的手動同步邏輯,造成 Presenter 比較笨重,維護起來會比較困難。

  2. 由于對視圖的渲染放在了 Presenter 中,所以視圖和 Presenter 的交互會過于頻繁。

  3. 如果 Presenter 過多地渲染了視圖,往往會使得它與特定的視圖的聯系過于緊密。一旦視圖需要變更,那么 Presenter 也需要變更了。

  4. 額外的代碼復雜度及學習成本。

MVP 和 MVC 的區別

  • MVP 是基于 MVC 模式上演變過來,與 MVC 有一定的相似性,Controller 和 Presenter 負責邏輯的處理,Model 提供數據,View 負責顯示。

  • 在 MVC 中,當 Model 被 Controller 更新后,Model 會直接通知 View 并更新顯示。而在 MVP 中,Model 與 View 不存在直接關系,這兩者之間間隔著的是 Presenter 層,其負責調控 View 與 Model 之間的間接交互,這也是 MVP 和 MVC 兩者之間最大的區別。

  • 此外 Presenter 與 View、Model 的交互使用接口定義交互操作可以降低耦合、簡化代碼。

MVP 與 Activity、Fragment 的生命周期

  • 問題原因:由于 Model 在進行異步操作,例如請求網絡數據,Presenter 持有 Activity 的強引用,如果在請求結束之前使得 Activity 被銷毀了,那么由于網絡請求還沒有返回,導致 Presenter 持有 Activity 對象,使得 Activity 無法被回收,此時就會發生內存泄露。(也許應用中出現一次兩次內存泄漏不會造成多大的影響,但應用在長時間使用后,若這些占據系統的大量內存的 Activity 得不到 GC 回收的話,最終會導致 OOM 的出現,就會直接 Crash 應用。)
  • 解決辦法:通過弱引用和 Activity、Fragment 的生命周期來綁定/解綁 View 解決這個問題,建立 BasePresenter,是一個泛型類 ,泛型類型為 View 角色要實現的接口類型 。
  • 好處:Presenter 不需要在構造函數中傳入 View 對象,而是在 View 中自由地通過Presenter 的 attachView 方法和 detachView 方法綁定和解綁 View 對象,除了 attachViewdetachView,我們還可以另外聲明 onResume 和 onStop 方法。

Model層的單獨優化

前面講了 View 和 Presenter 兩個層次,而 Model 層比較特殊,相對比較獨立的存在,幫上層拿數據,這是因為 MVP 模式的理念就是讓業務邏輯相互獨立。但如果每個網絡請求也獨立成單個 Model 的話,代碼操作起來也會非常麻煩,比如:

  1. 無法對所有 Model 統一管理;
  2. 每個 Model 對外提供的獲取數據方法不一樣,上層請求數據沒有規范;
  3. 代碼冗余,重復性代碼多;
  4. 對已存在的 Model 進行修改繁瑣;

那么就需要對 Model 進行封裝優化,使得 Model 層變成一個龐大且獨立單一的模塊,請求方式規范化,管理直觀化:

  1. 數據請求能夠單獨編寫和測試,無需配合上層界面測試;
  2. 統一以 DataModel 類作為數據請求層的入口,通過反射機制獲取對應的 Model;
  3. 無縫切換不同的數據源(網絡請求庫、緩存、數據庫);

MVP結構圖

MVP模式詳細結構圖

示例代碼

  • IBaseView:View接口中定義Activity的UI邏輯

    public interface IBaseView {
        void showLoading();
        void hideLoading();
        void showToast(String msg);
        void showErr();
        Context getContext();
    }
    
  • BaseActivity:主要是負責實現 BaseView 中通用的UI邏輯方法,如此這些通用的方法就不用每個Activity都要去實現一遍了。

    public abstract class BaseActivity extends AppCompatActivity implements IBaseView {
        // 加載條
        private ProgressDialog mProgressDialog;
    
        // 獲取Presenter實例,子類實現
        public abstract BasePresenter getPresenter();
        // 初始化Presenter的實例,子類實現
        public abstract void initPresenter();
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // 初始化Presenter
            initPresenter();
            if (getPresenter()!=null){
                getPresenter().attachView(this);
            }
            // 初始化進度條
            mProgressDialog = new ProgressDialog(this);
            mProgressDialog.setCancelable(false);
        }
        @Override
        public void showLoading() {
            if (!mProgressDialog.isShowing()) {
                mProgressDialog.show();
            }
        }
        @Override
        public void hideLoading() {
            if (mProgressDialog.isShowing()) {
                mProgressDialog.dismiss();
            }
        }
        @Override
        public void showToast(String msg) {
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
        }
        @Override
        public void showErr() {
            showToast("err....");
        }
        @Override
        public Context getContext() {
            return BaseActivity.this;
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (getPresenter() != null){
                getPresenter().detachView();
            }
        }
    }
    
  • MainActivity:繼承了BaseActivity抽象類,實現了getPresenter和initPresenter完成P層綁定。實現IMvpView接口中的showData達到UI更新操作。

    public class MainActivity extends BaseActivity implements IMvpView {
        TextView mTextView;
        MvpPresenter mvpPresenter;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main3);
            mTextView = findViewById(R.id.text);
        }
        @Override
        public BasePresenter getPresenter() {
            return mvpPresenter;
        }
        @Override
        public void initPresenter() {
            mvpPresenter = new MvpPresenter(); //初始化Presenter
            // mvpPresenter.attachView(this);// attachView放在抽象父類中
        }
        // 按鈕1
        public void getData(View view) {
            mvpPresenter.getData("normal");
        }
        // 按鈕2
        public void getDataForFailure(View view) {
            mvpPresenter.getData("failure");
        }
        // 按鈕3
        public void getDataForError(View view) {
            mvpPresenter.getData("error");
        }
        @Override
        public void showData(String data) {
            mTextView.setText(data);
        }
    }
    
  • IMvpView:IMvpView是Activity與Presenter層的中間層,它的作用是根據具體業務的需要,為Presenter提供調用Activity中具體UI邏輯操作的方法。

    /**
     * View接口是Activity與Presenter層的中間層,它的作用是根據具體業務的需要,
     * 為Presenter提供調用Activity中具體UI邏輯操作的方法。
     */
    public interface IMvpView extends IBaseView {
        /**
         * 當數據請求成功后,調用此接口顯示數據
         * @param data 數據源
         */
        void showData(String data);
    }
    
  • BasePresenter:處理View的生命周期;

    public class BasePresenter<V extends IBaseView> {
        // 綁定的view
        private V mView;
        // 綁定view,一般在初始化中調用該方法
        public void attachView(V mvpView) {
            this.mView = mvpView;
        }
        //  斷開view,一般在onDestroy中調用
        public void detachView() {
            this.mView = null;
        }
        // 是否與View建立連接,每次請求業務之前都要判斷
        public boolean isViewAttached() {
            return mView != null;
        }
        // 獲取當前連接的view
        public V getmView() {
            return mView;
        }
    }
    
  • MvpPresenter:該類是具體的邏輯業務處理類,負責請求數據,并對數據請求的反饋進行處理。

    public class MvpPresenter extends BasePresenter<IMvpView> {
        /**
         * 獲取網絡數據
         * @param userId
         */
        public void getData(String userId) {
            if (!isViewAttached()) {
                return;
            }
            //顯示進度條
            getmView().showLoading();
            DataModel.request1(UserDataModel.class)
                    .params(userId).execute(new MvpCallback() {
                @Override
                public void onSuccess(Object data) {
                    //調用view接口顯示數據
                    if (isViewAttached()) {
                        getmView().showData((String) data);
                    }
                }
                @Override
                public void onFailure(String msg) {
                    if (isViewAttached()) {
                        getmView().showToast(msg);
                    }
                }
                @Override
                public void onError() {
                    if (isViewAttached()) {
                        getmView().showErr();
                    }
                }
                @Override
                public void onComplete() {
                    if (isViewAttached()) {
                        getmView().hideLoading();
                    }
                }
            });
        }
    }
    
  • MvpCallback

    /**
     * Callback 接口是Model層給Presenter層反饋請求信息的傳遞載體,
     * 所以需要在Callback中定義數據請求的各種反饋狀態
     * 除了請求成功的回調方法,其他的像請求失敗,請求出錯這些方法我們做的事幾乎是一樣的。
     * 后期可以構建一個通用的BaseCallBack去處理請求的異常情況
     */
    public interface MvpCallback<T> {
        /**
         * 數據請求成功
         * @param data 請求到的數據
         */
        void onSuccess(T data);
        /**
         * 網絡返回數據失敗,請求成功
         * @param msg 無法正常返回數據的原因
         */
        void onFailure(String msg);
        /**
         * 請求數據失敗、無法聯網、缺少權限、內存泄露等等原因
         */
        void onError();
        /**
         * 無論執行上面那個方法,最后都會執行此方法,可以在這里設置隱藏加載框
         */
        void onComplete();
    }
    
  • DataModel:通過反射機制獲取對應的model

    public class DataModel {
        public static <T extends BaseModel> T request1(Class<T> cls) {
            // 聲明一個空的BaseModel
            T model = null;
            try {
                //利用反射機制獲得對應Model對象的引用
                model = (T) cls.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return model;
        }
    }
    
  • BaseModel:定義了對外的請求數據規則,包括設置參數的方法和設置Callback的方法,還可以定義一些通用的數據請求方法,比如說網絡請求的Get和Post方法。
    public abstract class BaseModel<T> {
        //數據請求參數
        protected String[] mParams;
        /**
         * 設置數據請求參數
         * @param args 參數數組
         */
        public BaseModel params(String... args) {
            mParams = args;
            return this;
        }
        // 添加Callback并執行數據請求
        // 具體的數據請求由子類實現
        public abstract void execute(MvpCallback<T> callback);
        // 執行Get網絡請求,此類看需求由自己選擇寫與不寫
        protected void requestGetAPI(String url, MvpCallback<T> callback) {
            //這里寫具體的網絡請求
        }
        // 執行Post網絡請求,此類看需求由自己選擇寫與不寫
        protected void requestPostAPI(String url, Map params, MvpCallback<T> callback) {
            //這里寫具體的網絡請求
        }
    }
    
  • getNetData:實現具體的Model請求時必須要重寫BaseModel的抽象方法execute

    public class UserDataModel extends BaseModel<String> {
        @Override
        public void execute(final MvpCallback<String> callback) {
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    // mParams 是從父類得到的請求參數
                    switch (mParams[0]){
                        case "normal":
                            callback.onSuccess("根據參數"+mParams[0]+"的請求網絡數據成功");
                            break;
                        case "failure":
                            callback.onFailure("請求失?。簠涤姓`");
                            break;
                        case "error":
                            callback.onError();
                            break;
                    }
                    callback.onComplete();
                }
            },2000);
        }
    }
    

效果圖

MVP效果圖

參考文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。