1. 程式人生 > >Android MVP 模式 架構 參考

Android MVP 模式 架構 參考

Android MVP 模式 架構 參考

 

Model-View-Presenter(MVP)

MVP的概念及優缺點網上一堆,如果大家不瞭解的可以去百度下,MVP只是個思想,沒有固定的鐵則,所以不同人對於MVP也有自己的理解,下面是本人對於MVP的理解(偏向於Passive View

  • View僅僅負責實現單純的、獨立的UI操作,儘量不要去維護資料(View層指ActivityFragment這類層級)
  • Model負責處理資料請求、業務邏輯,不涉及UI操作
  • PresenterMVP體系的控制中心,負責給ViewModel安排工作 ,什麼時候呼叫Model
    處理邏輯,什麼時候呼叫View反應結果,都是Presenter說了算
  • ViewModel均已介面的形式出現在Presenter中,Presenter通過呼叫 ViewModel的實現介面,來操作 ViewModel;同時Presenter也是以藉口的形式出現在View中,這樣PresenterView就是通過介面相互依賴了
  • Presenter是主動方,View是被動方,對於繫結到View上的資料,不是View呼叫Presenter主動拉取資料,而是Presenter主動將資料推給View

其呼叫順序如下:

  1. 使用者操作了介面
  2. View層響應使用者操作,然後向Presenter
    層發出請求
  3. Presenter層接受了View層的請求,呼叫Model層處理業務邏輯,然後呼叫View層將相應的結果反應出來

MVP的基類體系

上面說了那麼多,那麼如何在專案中構造MVP呢?不急,下面就是了~
首先我們先來構造一下MVP的基類體系,畢竟如果每個專案都重新寫一套MVP還是很麻煩的,構造一個MVP的基類體系,再根據專案,基於已有的MVP的基類體系,構造針對專案的MVP才是王道,省時省力,同時基類體系很可以幫我們更清晰的理解MVP

View層的介面基類

這是ActivityFragment需要實現的基類介面,裡面只是實現了一個獲取Activity的方法,主要用於在Presenter

中需要使用Context物件時呼叫,不直接在Presenter中建立Context物件,最大程度的防止記憶體洩漏

public interface IBaseXView {

    /**
     * 獲取 Activity 物件
     *
     * @return activity
     */
    <T extends Activity> T getSelfActivity();
}

Presenter層的介面基類

Presenter需要實現的介面

public interface IBaseXPresenter {

    /**
     * 判斷 presenter 是否與 view 建立聯絡,防止出現記憶體洩露狀況
     *
     * @return {@code true}: 聯絡已建立<br>{@code false}: 聯絡已斷開
     */
    boolean isViewAttach();

    /**
     * 斷開 presenter 與 view 直接的聯絡
     */
    void detachView();
}

View層的基類實現

View中通過IBaseXPresenter,來實現ViewPresenter的依賴,同時做了記憶體洩漏的預防處理。Activity通過getPresenter()來呼叫Presenter。另外,對於Fragment也可以仿照這樣寫。

public abstract class BaseXActivity<P extends IBaseXPresenter> extends Activity implements IBaseXView {
    private P mPresenter;
    
    /**
     * 建立 Presenter
     *
     * @return
     */
    public abstract P onBindPresenter();
    
    /**
     * 獲取 Presenter 物件,在需要獲取時才建立`Presenter`,起到懶載入作用
     */
    public P getPresenter() {
        if (mPresenter == null) {
            mPresenter = onBindPresenter();
        }
        return mPresenter;
    }

    @Override
    public Activity getSelfActivity() {
        return this;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        /**
         * 在生命週期結束時,將 presenter 與 view 之間的聯絡斷開,防止出現記憶體洩露
         */
        if (mPresenter != null) {
            mPresenter.detachView();
        }
    }
}

Presenter中的基類實現

Presenter中通過IBaseXView,來實現PresenterView的依賴,當每次對View進行呼叫時,先使用isViewAttach判斷一下,PresenterView之間的聯絡是否還在,防止記憶體洩漏。Presenter通過View暴露的介面IBaseXView,來控制View

public class BaseXPresenter<V extends IBaseXView> implements IBaseXPresenter {
    // 防止 Activity 不走 onDestory() 方法,所以採用弱引用來防止記憶體洩漏
    private WeakReference<V> mViewRef;

    public BaseXPresenter(@NonNull V view) {
        attachView(view);
    }

    private void attachView(V view) {
        mViewRef = new WeakReference<V>(view);
    }

    public V getView() {
        return mViewRef.get();
    }

    @Override
    public boolean isViewAttach() {
        return mViewRef != null && mViewRef.get() != null;
    }

    @Override
    public void detachView() {
        if (mViewRef != null) {
            mViewRef.clear();
            mViewRef = null;
        }
    }
}

以上是對ViewPresenter做處理,可以非常明確的看出他們之間的依賴關係,而對於Model,一般涉及到具體邏輯,資料請求在基類的初步構造中不用建立~~

具體專案中的MVP體系

上面是對廣大專案的MVP體系進行構造,但是因為專案與專案之間的不同,每個專案都有自己獨特的需求,如果直接使用上述的MVP體系,雖說也可以,但是卻沒有給開發者帶來最大程度的便利,僅僅是打了個大眾地基,我們還需要為我們要建造的大樓,添上大樓獨特的建造需求!

View的介面

沒啥好說的,直接繼承,再針對實際專案補充一些會常用到的方法

public interface IBaseView extends IBaseXView {

   /**
    * 顯示正在載入 view
    */
    void showLoading();
    
    /**
     * 關閉正在載入 view
     */
    void hideLoading();
    
    /**
     * 顯示提示
     * @param msg
     */
    void showToast(String msg);
}

View的實現

實現IBaseView中方法,並繼承BaseXActivity

public abstract class BaseActivity<P extends IBasePresenter> extends BaseXActivity<P> implements IBaseView {
    // 載入進度框
    private ProgressDialog mProgressDialog;
    
    @Override
    public void showLoading(){
        ......
    }
    
    @Override
    public void hideLoading(){
        ......
    }
    
    @Override
    public void showToast(String msg){
        ......
    }
    
    @Override
    protected void onDestroy() {
        hideLoading();
        super.onDestroy();
    }
}

Presenter的介面

本人在實際專案中對網路請求的取消用的也頻繁,所以寫上,你根據實際情況自己補充

public interface IBasePresenter extends IBaseXPresenter {

   /**
     * 取消網路請求
     *
     * @param tag 網路請求標記
     */
    void cancel(Object tag);

    /**
     * 取消所有的網路請求
     */
    void cancelAll();
}

Presenter的實現

這裡不止實現了IBasePresenter,,還實現了HttpResponseListener,網路請求響應介面

public abstract class BasePresenter<V extends IBaseView, T> extends BaseXPresenter<V> implements IBasePresenter, HttpResponseListener<T>{

    public BasePresenter(@NonNull V view) {
        super(view);
    }
    
      @Override
    public void cancel(@NonNull Object tag) {
        ......
    }

    @Override
    public void cancelAll() {
        ......
    }
    
    /**
     * 來自於 HttpResponseListener
     */
    @Override
    public void onSuccess(Object tag, T t) {
        
    }

    @Override
    public void onFailure(Object tag, HttpFailure failure) {

    }
}

HttpResponseListener

public interface HttpResponseListener<T> {
    /**
     * 網路請求成功
     *
     * @param tag 請求的標記
     * @param t   返回的資料
     */
    void onSuccess(Object tag, T t);

    /**
     * 網路請求失敗
     *
     * @param tag     請求的標記
     * @param failure 請求失敗時,返回的資訊類
     */
    void onFailure(Object tag, HttpFailure failure);
}

Model的實現

在專案中實現常用的傳送網路請求的方法,本人在專案中使用的是Retrofit + RxJava

public class BaseModel {

    /**
     * 傳送網路請求
     *
     * @param observable
     * @param callback
     * @param <T>
     */
    protected <T> void sendRequest(@NonNull Observable<T> observable, HttpResponseListener<T> callback) {
        ......
    }

    /**
     * 傳送網路請求
     *
     * @param tag
     * @param observable
     * @param callback
     * @param <T>
     */
    private <T> void sendRequest(@NonNull Object tag, @NonNull Observable<T> observable, HttpResponseListener callback) {
        ......
    }
    
    /**
     * 傳送網路請求
     *
     * @param observable 被觀察者
     * @param observer   觀察者
     * @param <T>
     */
    protected <T> void sendRequest(@NonNull Observable<T> observable, @NonNull HttpObserver<T> observer) {
       ......
    }

    /**
     * 傳送網路請求
     *
     * @param tag        請求標記
     * @param observable 被觀察者
     * @param observer   觀察者
     * @param <T>
     */
    protected <T> void sendRequest(@NonNull Object tag, @NonNull Observable<T> observable, @NonNull HttpObserver<T> observer) {
       ......
    }
}

MVP在具體頁面中的實戰

上面已經建立好了我們的MVP了,下面就是在真實頁面中使用了,是時候來一波操作了,let's go!!! 下面就以經典的登入頁面來舉例。

  • 根據google的建議,在使用MVP時,我們可以建立一個契約類Contacts
public final class LoginContacts {
   /**
     * view 層介面
     */
    public interface LoginUI extends IBaseView {
        /**
         * 登入成功
         */
        void loginSuccess();

        /**
         * 登入失敗
         */
        void loginFailure();
    }

    /**
     * presenter 層介面
     */
    public interface LoginPtr extends IBasePresenter{
        void login(String username, String password);
    }

    /**
     * model 層介面
     */
    public interface LoginMdl {
        void login(String username, String password, HttpResponseListener callbak);
    }
}

其實,就是將ViewPresenterModel中的實現介面寫在一起,看起來更加規範清晰,方便查詢

  • LoginModel
public class LoginMdl extends BaseModel implements LoginContacts.LoginMdl{
    
    /**
     * 登入
     *
     * @param username 使用者名稱
     * @param password 密碼
     * @param callbak  網路請求回撥
     */
    @Override
    public void login(String username, String password, HttpResponseListener callbak) {
        HashMap<String, String> map = new HashMap<>();
        map.put("username", username);
        map.put("password", Md5Util.encrypt(password));
        RequestBody body = ReqBodyHelper.createJson(map);
        // 傳送網路請求
        sendRequest(HttpUtils.getApi().getLoginInfo(body),callbak);
    }
}
  • LoginPtr
public class LoginPtr extends BasePresenter<LoginContacts.LoginUI, LoginBean> implements LoginContacts.LoginPtr, HttpResponseListener<LoginBean> {
    private LoginContacts.LoginMdl mLoginMdl;

    public LoginPtr(@NonNull LoginContacts.LoginUI view) {
        super(view);
        // 例項化 Model 層
        mLoginMdl=new LoginMdl();
    }

    @Override
    public void login(String username, String password) {
        //顯示進度條
        showLoading();
        mLoginMdl.login(username,password,this);
    }

    @Override
    public void onSuccess(Object tag, LoginBean t) {
        // 先判斷是否已經與 View 建立聯絡
        if (isViewAttach()) {
            // 隱藏進度條
            hideLoading();
            // 登入成功呼叫
            getView().loginSuccess();
        }
    }

    @Override
    public void onFailure(Object tag, HttpFailure failure) {
        if (isViewAttach()) {
            // 隱藏進度條
            hideLoading();
            // 登入失敗呼叫
            getView().loginFailure();
        }
    }
}
  • LoginActivity
public class LoginActivity extends BaseActivity<LoginContacts.LoginPtr> implements LoginContacts.LoginUI {
    private Button btn_login;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        btn_login=findViewById(R.id.btn_login);
        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 向 Presenter 層傳送登入請求
                getPresenter().login("admin","123456");
            }
        });
    }

    @Override
    public LoginContacts.LoginPtr onBindPresenter() {
        return new LoginPtr(this);
    }

    @Override
    public void loginSuccess() {
        Toast.makeText(this,"登入成功",Toast.LENGTH_LONG).show();
    }

    @Override
    public void loginFailure() {
        Toast.makeText(this,"登入失敗",Toast.LENGTH_LONG).show();
    }
}

總結

MVP是一種思想,並不是指某種固定框架,每個人都有自己獨特的理解。當前市場上除了MVP,還有MVCMVVM以及他們的變種模式!特別是當MVVM的出現,倍受開發者的推崇,可能會讓人覺得MVP已經沒落!其實不然,沒有哪種模式是沒落的,哪怕是MVC,也有許多大公司仍然在堅持使用,而且使用的很好!需要根據實際專案需求以及 個人對這些模式的掌控力來選擇專案設計模式,不一定要用最新潮,自己用的順,用的好,那就是適合你的設計模式!


連結:https://www.jianshu.com/p/19283a3f61de