android:MVP架構模式的優雅封裝
簡介
關於Android程式的構架, 主流的不外乎以下幾種:MVC、MVP和MVVM。
MVC:相對於較為落後,耦合度太高、職責不明確,不易於維護。
MVVM:使用DataBinding,普及性不如MVP。
此外,Google官方提供了Sample程式碼來展示MVP模式的用法,因此主流還是選擇MVP架構。
因本文主要講的是MVP模式的優雅封裝,MVVM模式在此就不作贅述,後續文章會講到。
MVC:
提到MVP就不得不提到MVC,關於MVC架構,可以看下面這張圖

image.png
MVC工作原理:
MVC即Model View Controller,簡單來說就是通過controller的控制去操作model層的資料,並且返回給view層展示,具體見上圖。當用戶出發事件的時候,view層會發送指令到controller層,接著controller去通知model層更新資料,model層更新完資料以後直接顯示在view層上,這就是MVC的工作原理。
這種原理就會造成一個一個致命的缺陷:當我們把很多業務邏輯寫在activity中時,activity既充當了View層,又充當了Controller層。因此,耦合性極高,各種業務邏輯程式碼和View程式碼混合在一起你中有我我中有你,如果要修改一個需求,改動的地方可能相當多,維護起來十分不便。
作為一個追求優雅的程式猿,這種架構必然要被拋棄。
MVP:

image.png
概念
MVP即Model、View、Presenter
View:負責檢視部分展示、檢視事件處理。Activity、Fragment、Dialog、ViewGroup等呈現檢視的元件都可以承擔該角色。
Model:負責資料的請求、解析、過濾等資料層操作。
Presenter:View和Model互動的橋樑。
優勢
單一職責
Model、View、Presenter只處理某一類邏輯
解耦
Model層修改和View層修改互不影響
面向介面程式設計,依賴抽象
Presenter和View互相持有抽象引用,對外隱藏內部實現細節
可能存在的問題
1、Model進行非同步操作,獲取結果通過Presenter回傳到View時,出現View引用的空指標異常
2、Presenter和View互相持有引用,解除不及時造成的記憶體洩漏。
因此,在進行MVP架構設計時需要考慮Presenter對View進行回傳時,View是否為空?
Presenter與View何時解除引用即Presenter能否和View層進行生命週期同步?
好了,說了這麼多廢話,總之一句話,MVP好。下面我們來看看具體如何優雅的實現MVP的封裝。
MVP架構優雅的封裝
1、首先,我們定義一個BaseView
/** * 檢視基類 */ public interface BaseView { }
上面說過,
如果Presenter與View不及時解除引用關係,那麼記憶體洩漏乃至記憶體溢位就是必然。
具體來說,
當Presenter物件持有一個或多個大型Activity的引用,如果該物件(P)不能被系統回收,那麼當這些Activity不再使用時,這個Activity也不會被系統回收,這樣一來便出現了記憶體洩漏的情況。在應用中內出現一次兩次的記憶體洩漏或許不會出現什麼影響,但是在應用長時間使用以後,若是這些佔據大量記憶體的Activity無法被GC回收的話,最終會導致OOM的出現,就會直接Crash應用。
我們當然不會坐視這種情況的發生,解決的思路就是,
我們將Presenter的生命週期和View層的生命週期繫結在一起,給Presenter定義兩個方法,一個繫結View層,一個解綁View層,在需要的時候進行繫結,不需要的時候進行解綁就可以了。
於是就有了下面這個定義。
2、將Presenter的生命週期和View層的生命週期繫結
/** * 控制器介面: * 定義P層生命週期與 V層同步 */ public interface IPresenter<V extends BaseView> { void onMvpAttachView(V view, Bundle savedInstanceState); void onMvpStart(); void onMvpResume(); void onMvpPause(); void onMvpStop(); void onMvpSaveInstanceState(Bundle savedInstanceState); void onMvpDetachView(boolean retainInstance); void onMvpDestroy(); }
為了程式碼的優雅性,我們對它進行一次封裝
/** * 控制器基類: * Presenter生命週期包裝、View的繫結和解除,P層實現的基類 */ public class BasePresenter<V extends BaseView> implements IPresenter<V> { private WeakReference<V> viewRef; protected V getView() { return viewRef.get(); } protected boolean isViewAttached() { return viewRef != null && viewRef.get() != null; } private void _attach(V view, Bundle savedInstanceState) { viewRef = new WeakReference<V>(view); } @Override public void onMvpAttachView(V view, Bundle savedInstanceState) { _attach(view, savedInstanceState); } @Override public void onMvpStart() { } @Override public void onMvpResume() { } @Override public void onMvpPause() { } @Override public void onMvpStop() { } @Override public void onMvpSaveInstanceState(Bundle savedInstanceState) { } private void _detach(boolean retainInstance) { if (viewRef != null) { viewRef.clear(); viewRef = null; } } @Override public void onMvpDetachView(boolean retainInstance) { _detach(retainInstance); } @Override public void onMvpDestroy() { } }
3、對於View層,我們一般都會寫一個BaseActivity
/** * @description 在此類中新增自己的基類功能 */ public class BaseActivity extends FragmentActivity { protected void openActivity(String action) { openActivity(action, null); } public void showEnsureDialog(String message) { } }
4、我們再寫一個繫結生命週期的BaseMvpActivity包裝類
/** * MVP的Activity基類: * 純粹的 MVP 包裝,不要增加任何View層基礎功能 * 如果要新增基類功能,請在{@link BaseActivity} 中新增 */ public abstract class BaseMvpActivity<P extends IPresenter> extends BaseActivity implements BaseView { protected P mPresenter; protected abstract P createPresenter(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPresenter = createPresenter(); if (mPresenter == null) { throw new NullPointerException("Presenter is null! Do you return null in createPresenter()?"); } mPresenter.onMvpAttachView(this, savedInstanceState); } @Override protected void onStart() { super.onStart(); if (mPresenter != null) { mPresenter.onMvpStart(); } } @Override protected void onResume() { super.onResume(); if (mPresenter != null) { mPresenter.onMvpResume(); } } @Override protected void onPause() { super.onPause(); if (mPresenter != null) { mPresenter.onMvpPause(); } } @Override protected void onStop() { super.onStop(); if (mPresenter != null) { mPresenter.onMvpStop(); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mPresenter != null) { mPresenter.onMvpSaveInstanceState(outState); } } @Override protected void onDestroy() { super.onDestroy(); if (mPresenter != null) { mPresenter.onMvpDetachView(false); mPresenter.onMvpDestroy(); } } }
5、我們以登入為例,定義一個契約類
/** * 契約介面類: * P層與 V層介面定義 */ public class LoginContract { public interface ILoginView extends BaseView { /** * 登入成功 */ void LoginSuccess(); /** * 登入失敗 * * @param msg */ void LoginFailed(String msg); } public interface ILoginPresenter extends IPresenter<ILoginView> { /** * 登入 */ void login(String username, String password); } }
6、我們再定義一個登入的Presenter的實現類,在這個類中,完成互相訪問。
/** * 控制器實現類 */ public class LoginPresenterImpl extends BasePresenter<LoginContract.ILoginView> implements LoginContract.ILoginPresenter { @Override public void login(String username, String password) { //先進行非空判斷 if (isViewAttached()) { handleLogin(getView(), username, password); } } private void handleLogin(LoginContract.ILoginView view, String username, String password) { if (username.isEmpty() || password.isEmpty()) { view.LoginFailed("賬號和密碼不能為空"); } else if (password.length() < 6 || password.length() > 20) { view.LoginFailed("密碼須在6-20位之間"); } else { if (username.equals("mvp")) { if (password.equals("123456")) { view.LoginSuccess(); } else { view.LoginFailed("密碼錯誤"); } } else { view.LoginFailed("使用者名稱錯誤"); } } } @Override public void onMvpAttachView(LoginContract.ILoginView view, Bundle savedInstanceState) { super.onMvpAttachView(view, savedInstanceState); } /** * 重寫P層需要的生命週期,進行相關邏輯操作 */ @Override public void onMvpResume() { super.onMvpResume(); } }
7、到這裡,我們的封裝基本完成,我們現在來看看我們的LoginActivity是怎麼樣的。
public class LoginActivity extends BaseMvpActivity<LoginContract.ILoginPresenter> implements LoginContract.ILoginView { @BindView(R.id.et_username) EditText etUsername; @BindView(R.id.et_password) EditText etPassword; @BindView(R.id.btn_login) Button btnLogin; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mvp); ButterKnife.bind(this); } @Override protected LoginContract.ILoginPresenter createPresenter() { return new LoginPresenterImpl(); } @OnClick({R.id.btn_login}) public void onViewClicked(View view) { switch (view.getId()) { case R.id.btn_login: String username = etUsername.getText().toString().trim(); String password = etPassword.getText().toString().trim(); mPresenter.login(username, password); break; } } @Override public void LoginSuccess() { Toast.makeText(this, "LoginSuccess", Toast.LENGTH_SHORT).show(); } @Override public void LoginFailed(String msg) { Toast.makeText(this, "LoginFailed", Toast.LENGTH_SHORT).show(); } }
這樣,是不是看起來,清爽多了。
8、最後,總結:
Mvp模式很好的將View層和Presenter解耦,View層和Presenter層的修改互不影響,並且符合軟體設計原則之單一職責,提高了程式碼的靈活性和可擴充套件性。最後二者之間通過抽象進行關聯,使之可以互相訪問。
從MVC到最簡單的MVP架構,我們解決了MVC的資料層和檢視層耦合的問題;隨之而來的是記憶體洩露的問題,通過設定對應的繫結解綁方法來解決這個問題;之後又是程式碼冗餘的問題,於是利用Java的多型性,我們將重複性工作交由基類去完成,子類繼承基類重寫對應方法即可。而實際上我們只需要修改上面Presenter中的構造程式碼,不需要在構造中傳遞V層了,然後再寫一個繫結和解綁的方法,最後修改Activity建立Presenter時進行繫結,在onDestroy中進行解綁。