Android從零開始(第三篇)MVP架構搭建
這幾天都在研究如何搭建一個實用穩固的MVP架構作為快速開發的基底。 也糾結了很久Presenter層該如何複用,在網上查閱了很多資料之後仍然沒能找到一個適用的辦法,有的寫法單純是為了presenter的複用而寫,卻給其他模組增負擔;有的實現的手法過於僵硬,不符合寫程式碼的原則。 在看完各種奇奇怪怪的實現思路之後,自己內心也有了一個實現presenter複用的一套方法,不過還不知道可不可行,到時擼完了可行再貼出來。
走過路過點歌Start O(∩_∩)O Github專案地址
這篇文章先擼一遍MVP的基本框架搭建,看完這篇文章你能學會:
- 一個還不錯的Mvp框架結構是怎樣的
- Mvp框架如何避免記憶體洩漏
- Presenter層如何複用? 這一個以後確定可行再擼
順著我的思路來一遍,先構造基類: 首相是對View層的基類下手, IBaseView
package com.example.administrator.mvpframedemo.base; public interface IBaseView { } 複製程式碼
實不相瞞,這個我一個方法都沒定義。看到網上有很多人會把showToast()等這樣的方法定義在這裡喔我就不同意了,因為這些太重複固定的我覺得放在基類讓每個子類去實現實在麻煩,所以我怎麼做呢?我把showToast這樣重複的實現放在BaseActivity;下面一起看一下 BaseActivity
public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { initBeforeCreate(); super.onCreate(savedInstanceState); setContentView(getLayoutId()); init(savedInstanceState); initData(); initView(); logic(); } protected abstract void initBeforeCreate(); protected abstract int getLayoutId(); protected abstract void init(Bundle savedInstanceState); protected abstract void initData(); protected abstract void initView(); protected abstract void logic(); protected void showToast (String toastStr) { Toast.makeText(this, toastStr, Toast.LENGTH_SHORT).show(); }; } 複製程式碼
這是在沒啥好說,這裡釋放了更BaseActivity有關的,而不是跟Mvp有關的。至於你瞭解的BaseActivity中要處理presenter繫結,解綁這樣的操作我會另外建一個BaseMvpActivity來做專門針對Mvp的處理的。保持這樣結構的整潔還是感覺神清氣爽的;當然BaseMvpActivity還是要繼承BaseActivity的;萬一哪天天收的說這個模組我要用MVC之類的時候也好處理一點。 接下來就是 BasePresenter
public class BasePresenter<V extends IBaseView>{ private WeakReference<V> mViewRef; public V mView; public void attachView(V view) { mViewRef = new WeakReference<V>(view); mView = mViewRef.get(); } public void detachView() { mViewRef.clear(); mView = null; } } 複製程式碼
這裡定義了attachView()以及detachView()兩個介面;這是為啥?還有為什麼要有mViewRef? 首先說為什麼要attachView()?這個其實只要你敲過一些簡單的mvp的程式碼就會知道,每一次都要寫這樣的程式碼:
MainActivity{ // 這一句程式碼做了兩個事情,①View層建立自己適合的Presenter,②然後把自己傳給Presenter完成兩者的繫結 Presenter presenter = new Presenter(MainActivity.this); } 複製程式碼
那麼attachView()就是完成②的事情,至於①是留給View層自己去實現的,後面會說到。 所以attachView()誕生的原因就瞭解了,那麼detachView()設計的原因呢? detachView()還有mViewRef的出現都是為了解決記憶體洩漏而存在的。那麼是怎麼解決記憶體洩漏的存在呢? 當一個Activity在顯示的時候退出了,GC在感覺記憶體緊張的時候會想把這個Activity給回收掉,但是此時presenter物件是持有Activity物件的,所以GC就沒辦法回收了,這樣就存在洩漏的隱患了。這種持有activity物件而引起記憶體洩漏是非常常見的原因。 所以我們使用deacttach()以及mViewRef(弱引用)來解除兩者的繫結,讓GC隨心所欲。那什麼時候解綁呢?在Activity的onDestroy()生命週期的時候合適。 那麼接下來就是: BaseMvpActivity
public abstract class BaseMvpActivity<T extends BasePresenter> extends BaseActivity implements IBaseView { protected T mPresenter; @Override protected void init(Bundle savedInstanceState) { mPresenter = bindPresenter(); mPresenter.attachView(this); } protected abstract T bindPresenter(); @Override protected void onDestroy() { super.onDestroy(); mPresenter.detachView(); } } 複製程式碼
這個BaseMvpActivity裡面做了針對Mvp的事情,包括定義bindPresenter()建立自己合適的presenter,然後執行presenter.attachView(this),將兩者進行繫結;最後在onDestroy()方法中接觸兩者的繫結。
--------------------------------------人工分割線---------------------------------------------------------------------- 到這裡,關於Mvp的基類設計好像就差不多了。 然後來模擬看看實際上要進行的業務: 登入頁面要請求登入 第一步:在constract(合約層,維護P層與V層的關係) 建立一個LoginConstract
public interface LoginContract { abstract class LoginPresenter extends BasePresenter<LoginView>{ public abstract void login(String name, String password); } interface LoginView extends IBaseView { void showTips(String str); } } 複製程式碼
在合約裡面定義LoginPresenter以及LoginView的介面;實現交給其他地方。 在這裡開發者就要明白登入的邏輯,例如:首先View利用presetner發起登入(login)請求,請求完成之後View要給使用者顯示結果(showTips);所以在上面定義的介面也是這麼來的。 實現: LoginPresenter:
publicclass LoginPresenter extends LoginContract.LoginPresenter { ILoginModel loginModel; @Override public void login(String name, String password) { loginModel = new LoginModel(); loginModel.login(name, password, new LoginCallBack()); } private class LoginCallBack implements ICallBack<LoginDomain, Exception> { @Override public void onSuccess(LoginDomain result) { mView.showTips("登入成功"); } @Override public void onFail(Exception error) { mView.showTips("登入失敗"); } } } 複製程式碼
簡簡單單,login()方法需要用到model層去幫忙獲取資料。所以例項化合適的model,然後呼叫它取資料的介面。 我一般會把model層分為interface以及impl兩層,一個定義介面,一個實現。 不過關於Presenter與Model層的互動問題需要說明一下:因為Model層取資料大多未非同步操作,所以通常使用介面回撥的方式。所以在呼叫Model的login()取資料的時候傳遞一個回撥物件來實現非同步。 LoginModel:
public class LoginModel implements ILoginModel { @Override public void login(String username, String password, ICallBack<LoginDomain, Exception> callBack) { // 模擬一部網路獲取 try { Thread.sleep(2000); callBack.onSuccess(new LoginDomain("周潤發","123")); } catch (InterruptedException e) { e.printStackTrace(); callBack.onFail(new Exception()); } } } 複製程式碼
到了這裡其實只有一些細節的問題了,關於presenter與model層的ICallBack回撥如何統一規範等等都是小事。 最重要的是 presenter如何複用 ?敬請期待我之後的博文。
最後是我的專案的目錄結構:
