MVP 在專案中的最佳實戰(封裝篇)

夏至的稻穗發表於2017-06-26

作者 夏至,歡迎轉載,但請保留這段申明
blog.csdn.net/u011418943/…

說到 MVP ,大家應該都不陌生了,由於其高度解耦等優點,越來越多的專案使用這個設計模式;然而,優點雖在,缺點也不少,其中一個就是類多了很多,而且 V 與 P 直接要專案通訊,那麼 P 就得持有 V 得例項,但如果 activity 掛掉了,如果沒有對 V 進行釋放,又有導致記憶體溢位得問題,而且,那麼多的介面函式,看得人眼花繚亂,也使得很多人在使用這個模式的時候望而卻步。
如果對 MVP 不瞭解,可以看我以前的這兩篇文章:
MVP 設計模式理解,實戰理解MVP
MVP+多執行緒+斷電續傳 實現app線上升級庫 (手把手教你打造自己的lib)

迴歸原題,最近把一個專案改成 MVP 的模式,用以前的方式,寫完之後,那麼多介面類,除了很多重複了之外,和別人共同開發也是個問題;那麼,封裝一些 base 基類就顯得尤為重要了。當然,一千個人中有一千個哈姆雷特,這裡就提供一下我的思路,大家可以結合自己的理解參考一下。
專案型別: 類似一個 雷豹清理大師的清理工具,先上傳個簡單的 效果圖吧,這樣會比較好理解一下:

1、封裝 BaseView

BaseView 的封裝中,我們把每個子類都會用到的方法放在這裡,由於是清理工具類的專案,那麼就有一個掃描的過程,還有載入失敗,所以,baseview 如下:

public interface BaseView {
    void fail(String errorMsg);
    // 掃描狀態更新,根據需要更新
    void scanStatusShow(int current,int maxsize,long size,String path);
}複製程式碼

當然,這裡的 baseview 只是我這個專案的,實際上,你還可以在上面新增 夜間模式切換,顯示隱藏載入進度條等公有方法。

2、封裝 BasePresenter

上面中,我們提到,presenter 是持有view 的例項的,假如activity崩潰,而persenter繼續持有,那麼勢必會導致記憶體洩漏的問題的;所以,我們希望在activity崩潰時,也把 view 去掉;
那麼,basepresenter 時用抽象類好?還是介面類呢?比如網上很多人這樣寫:

public abstract class BasePresent{
    public T view;
    public void attach(T view){
        this.view = view;
    }
    public void detach(){
        this.view = null;
    }
}複製程式碼

這裡用了一個抽象類來實現,但我們參考 google 的 mvp 原始碼時,它的 basepresenter 是一個介面,就一個 start() 方法,然後再用一個契約類來實現的,如下:

/**
 * This specifies the contract between the view and the presenter.
 */
public interface AddEditTaskContract {
    interface View extends BaseView {
        void showEmptyTaskError();
        void showTasksList();
        void setTitle(String title);
        void setDescription(String description);
        boolean isActive();
    }
    interface Presenter extends BasePresenter {
        void saveTask(String title, String description);
        void populateTask();
        boolean isDataMissing();
    }
}複製程式碼

可以看到,它把相關的 view 和persenter 都放在 contract 這個類中,這樣做的好處在於,方便後期的維護,類的實現方法和介面都在這,方便查詢與除錯,所以,我們封裝的時候,也參考這種模式。但思考一個問題,我們的 basepresenter 是用抽象類好呢?好像介面呢?

答案是抽象,為什麼?
我們知道,介面是一個特殊的抽象類,介面裡面的方法都是抽象方法,如果定義成介面函式,則裡面的方法必須實現,則在 baseactivity 或者 basefragment 繼承時重寫了方法,而在 具體子類的 presenter 的時候也重寫了方法,這樣就多餘了,所以,書上看到這介面和抽象類的總結,覺得還不錯,如下:

  • 優先使用介面
  • 在既要定義子類的行為,又要為子類提供公共的功能時應選擇抽象類

所以,這裡的 basepresenter 就用 抽象類會好一點。

/**
 * T 泛型,指的是baseview,當然也可以是其他的view 型別
 * Created by zhengshaorui on 2017/6/22.
 */

public abstract class BasePresenter {
    // 獲取 view 的view 例項
    T view;
     void onAttach(T view){
         this.view = view;
     };

    // 解綁 view 層
     void onDetch(){
         this.view = null;
     };
    // view 資料的開始,一般再 oncreate 或者 onresume 中
   // void start();
}複製程式碼

可能你會疑問,這樣的話,我們的契約類怎麼寫?別急,往下看。

3、契約類 contract

回顧一下自己以前寫的 mvp 小demo,我們是不是 v 一個介面,p一個介面,除了結構上給人類很多之外,而且還大部分是重複的,涉及到 v 層資料的更新,p在獲取了v的例項之後,通過觀察者模式,獲取model的資料更新到v,那麼這裡介面就存在重複的,我們可以用基類繼續封裝;

而且mvp中的p,我們說過,它就是個紐帶,那麼就不要新增什麼邏輯性的東西在這裡,保證 p 的輕薄,v 就是更新 UI 的。

ok,首先,先看一下我的 contract 類的封裝:

public interface ContractUtils {

    /*=============一鍵減速契約類===============*/
    // 一鍵加速contract
    interface ISpeedUpView  extends SpeedUpBaseInterface {
        void getMemory(long availsize,String total);
    }
    //presenter 的介面類
    interface ISpeedUpPresenter extends SpeedUpBaseInterface{
        void startSpeedup();
    }

    /* ==============輕度清理契約類==================*/
    interface IClearView extends BaseView {
        void scanComplete(List mInfoList);
    }

    interface  IClearPresenter extends BaseView{
        void scanComplete(List mInfoList);
        void startclear();
    }
    .....
}複製程式碼

ok,在 contractUtils 類中,我把v與p的介面都放在這裡,這樣方便查閱,然後關於他們之間更新資料重複的介面,再封裝一次,其中 SppedUpBaseInterface 如下,繼承自baseview:

public interface SpeedUpBaseInterface extends BaseView {
    void scanComplete(List datas);
    void updateMemory(String msg);
}複製程式碼

以後在除錯的時候,只要過來檢視這個共有契約類就方便多了。

4、封裝baseactiviy

這個大家在平時都有做的,我們可以在這裡封裝activity之間的啟動動畫,許可權管理,還有共有方法等等,而 MVP 這個設計模式,當然也是所有的 activity 都支援的,所有,封裝如下,這裡只展示 mvp 部分:

public abstract class BaseActivity,T extends BasePresenter> extends Activity  {
    public T mPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // TODO:OnCreate Method has been created, run FindViewById again to generate code
        ..... 
       //這裡初始化 mvp
        mPresenter = getPresenter();
    }
    @Override
    protected void onResume() {
        super.onResume();
        if (mPresenter != null) {
            mPresenter.onAttach((V) this);
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.onDetch();
        }
    }
    /**
     * 不同activity的佈局介面
     * @return
     */
    public abstract int getLayoutId();
    public abstract T getPresenter();

}複製程式碼

在所有的 onresume 中,將 view 例項給 presenter ,在 ondestroy 中,銷燬其中的view,而 baseactivity 後面的泛型,就是根據不同的子類來的。最後用一個抽象方法,getPresenter 把子類的 presenter 拿出來,都在 oncreate 中初始化;

5、子類中的呼叫

子類中的呼叫,這裡展示 mvp 部分,比如一鍵加速這裡:
View:

public class SpeedUpActivity extends BaseActivity
        implements ContractUtils.ISpeedUpView {
    ....
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initdata();
    }

    @Override
    public int getLayoutId() {
        return R.layout.activity_speedup;
    }

    @Override
    public SpeedUpPresenter getPresenter() {
        mSpeedUpPresenter = new SpeedUpPresenter(this,this);
        return mSpeedUpPresenter;
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    private List mlist = new ArrayList<>();

    public void speedup(View view) {


        mSpeedUpPresenter.startSpeedup();


    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

    }



    @Override
    public void getMemory(long availsize, String total) {
        mCircleRunView.setMemeryMsg(availsize," / "+total);
    }

    @Override
    public void updateMemory(String msg) {
        if (msg.equals("0")){
            Toast.makeText(this, "您的電視非常流暢", Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(this, "共為您節省了: "+msg, Toast.LENGTH_SHORT).show();
        }
    }



    @Override
    public void fail(String errormsg) {
        lg.d("speedupviewerror: "+errormsg);
    }

    @Override
    public void scanStatusShow(int current, int maxsize, long size,String path) {
        mLoadingText.setText(getString(R.string.loading,current,maxsize));
    }





}複製程式碼

注意子類整合 baseactivity 的 view 和 presenter,還有抽象方法 getPresenter 即可,基本沒啥區別

至於 presenter 的實現,則大家修改一下自己的就行了,比如我的:

public class SpeedUpPresenter extends BasePresenter implements
            ContractUtils.ISpeedUpPresenter{

    private ISpeedUpModel mSpeedUpModel;
    private ContractUtils.ISpeedUpView mISpeedUpView;
    private Context mContext;
    public SpeedUpPresenter(ContractUtils.ISpeedUpView iSpeedUpView, Context context) {
        mContext = context;
        mISpeedUpView = iSpeedUpView;
        mSpeedUpModel = new SpeedUpModel(context,this);
        mSpeedUpModel.startScanRunInfo();
    }
    .....
 }複製程式碼

關於model的思考

上面,我們對 view 和 presenter 都進行了封裝,那model是否也該封裝呢?這個不好說,根據自己的專案來,不過一般很多封裝,畢竟這個資料邏輯處理的,都是單獨出來的;
不過,model 我一般把用單例的,而且比如專案中的深度清理,model 的掃描和資料的獲取是在presenter 中去初始化後再去掃描嗎?

No No No,我們說過 p 只是一個觀察者,它只負責你model的資料更新後實時更新給v,那麼你 model 的怎麼做,我就不管了,我只要你的資料就行,想深度掃描這種耗時的,我是在 application裡面初始化的;但記得的一點是,不要在application 裡做過多的操作,不然會拖慢app的啟動的。

封裝後與以前的區別

  • v 與 p 的介面,我們都可以用契約類來實現了,除了方便查閱除錯之外,還減少了很多類
  • 封裝了 v 與 p 公有的方法,減少累贅程式碼
  • 關於model的思考

好,以上就是這是我對 MVP 封裝的初步理解,如有錯誤,也歡迎大家留言指正,謝謝。

相關文章