前言
在學習了 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角色上抽離出來。
在通常開發中,
View
是指 Activity / Fragment,不過,Presenter 和 Activity 通過定義一個 view 接口進行關聯,而 Presenter 和 Model 是通過Callback
接口進行關聯。
- View接口:顯示提示框、數據更新;
- Callback接口:請求數據時反饋狀態(成功、失敗和異常等等);
MVP的優缺點
優點
分離了視圖邏輯和業務邏輯,降低了耦合。
Activity 只處理生命周期的任務,代碼變得更加簡潔。
視圖邏輯和業務邏輯分別抽象到了 View 和 Presenter 的接口中去,提高代碼的可閱讀性。
Presenter 被抽象成接口,可以有多種具體的實現,所以方便進行單元測試。
把業務邏輯抽到 Presenter 中去,避免后臺線程引用著 Activity 導致 Activity 的資源無法被系統回收從而引起內存泄露。
Presenter 代碼可復用,一個 Presenter 可以用于多個 View,而不需要更改 Presenter 的邏輯。
缺點
Presenter 中除了應用邏輯以外,還有大量的 View->Model,Model->View 的手動同步邏輯,造成 Presenter 比較笨重,維護起來會比較困難。
由于對視圖的渲染放在了 Presenter 中,所以視圖和 Presenter 的交互會過于頻繁。
如果 Presenter 過多地渲染了視圖,往往會使得它與特定的視圖的聯系過于緊密。一旦視圖需要變更,那么 Presenter 也需要變更了。
額外的代碼復雜度及學習成本。
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 對象,除了
attachView
和detachView
,我們還可以另外聲明 onResume 和 onStop 方法。
Model層的單獨優化
前面講了 View 和 Presenter 兩個層次,而 Model 層比較特殊,相對比較獨立的存在,幫上層拿數據,這是因為 MVP 模式的理念就是讓業務邏輯相互獨立。但如果每個網絡請求也獨立成單個 Model 的話,代碼操作起來也會非常麻煩,比如:
- 無法對所有 Model 統一管理;
- 每個 Model 對外提供的獲取數據方法不一樣,上層請求數據沒有規范;
- 代碼冗余,重復性代碼多;
- 對已存在的 Model 進行修改繁瑣;
那么就需要對 Model 進行封裝優化,使得 Model 層變成一個龐大且獨立單一的模塊,請求方式規范化,管理直觀化:
- 數據請求能夠單獨編寫和測試,無需配合上層界面測試;
- 統一以
DataModel
類作為數據請求層的入口,通過反射機制獲取對應的 Model; - 無縫切換不同的數據源(網絡請求庫、緩存、數據庫);
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); } }