Android從零開始(第三篇)MVP架構搭建

bigname22發表於2019-01-23

這幾天都在研究如何搭建一個實用穩固的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:

public  class 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如何複用?敬請期待我之後的博文。

最後是我的專案的目錄結構:

在這裡插入圖片描述

相關文章