一篇文章徹底搞懂 MVP

本文由玉剛說寫作平臺提供寫作贊助,版權(quán)歸玉剛說微信公眾號所有
原作者:Zackratos
版權(quán)聲明:未經(jīng)玉剛說許可,不得以任何形式轉(zhuǎn)載

什么是 MVP

MVP 全稱:Model-View-Presenter ;MVP 是從經(jīng)典的模式 MVC 演變而來,它們的基本思想有相通的地方:Controller/Presenter 負(fù)責(zé)邏輯的處理,Model 提供數(shù)據(jù),View 負(fù)責(zé)顯示(摘自百度百科)。

為什么要使用 MVP

在討論為什么要使用 MVP 架構(gòu)之前,我們首先要了解傳統(tǒng)的 MVC 的架構(gòu)的特點(diǎn)及其缺點(diǎn)。

首先看一下 MVC 架構(gòu)的模型圖,如下

mvc

這個(gè)圖很簡單,當(dāng) View 需要更新時(shí),首先去找 Controller,然后 Controller 找 Model 獲取數(shù)據(jù),Model 獲取到數(shù)據(jù)之后直接更新 View。

在 MVC 里,View 是可以直接訪問 Model 的。從而,View 里會(huì)包含 Model 信息,不可避免的還要包括一些業(yè)務(wù)邏輯。 在 MVC 模型里,更關(guān)注的 Model 的不變,而同時(shí)有多個(gè)對 Model 的不同顯示,即 View。所以,在 MVC 模型里,Model 不依賴于 View,但是View 是依賴于 Model 的。不僅如此,因?yàn)橛幸恍I(yè)務(wù)邏輯在 View 里實(shí)現(xiàn)了,導(dǎo)致要更改 View 也是比較困難的,至少那些業(yè)務(wù)邏輯是無法重用的。

這樣說可能會(huì)有點(diǎn)抽象,下面通過一個(gè)簡單的例子來說明。

假設(shè)現(xiàn)在有這樣一個(gè)需求,Activity 中有一個(gè) Button 和一個(gè) TextView,點(diǎn)擊 Button 時(shí)會(huì)請求網(wǎng)絡(luò)獲取一段字符串,然后把字符串顯示在 TextView 中,按照正常的邏輯,代碼應(yīng)該這么寫

public class MVCActivity extends AppCompatActivity {

    private Button button;
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        button = findViewById(R.id.button);
        textView = findViewById(R.id.text_view);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new HttpModel(textView).request();
            }
        });
    }
}



public class HttpModel {
    private TextView textView;

    public HttpModel(TextView textView) {
        this.textView = textView;
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            textView.setText((String) msg.obj);
        }
    };

    public void request() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    Message msg = handler.obtainMessage();
                    msg.obj = "從網(wǎng)絡(luò)獲取到的數(shù)據(jù)";
                    handler.sendMessage(msg);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

代碼很簡單,當(dāng)點(diǎn)擊 Button 的時(shí)候,創(chuàng)建一個(gè) HttpModel 對象,并把 TextView 對象作為參數(shù)傳入,然后調(diào)用它的 request 方法來請求數(shù)據(jù),當(dāng)請求到數(shù)據(jù)之后,切換到主線程中更新 TextView,流程完全符合上面的 MVC 架構(gòu)圖。

但是這里有個(gè)問題,首先很顯然,HttpModel 就是 Model 層,那么 View 層和 Controller 層呢,我們分析一下 View 層和 Model 層分別干了什么事,在本例中,View 層主要做的事就是當(dāng)獲取到網(wǎng)絡(luò)數(shù)據(jù)的時(shí)候,更新 TextView,Controller 層主要做的事就是創(chuàng)建 HttpModel 對象并調(diào)用它的 request 方法,我們發(fā)現(xiàn) MVCActivity 同時(shí)充當(dāng)了 View 層和 Controller 層。

這樣會(huì)造成兩個(gè)問題,第一,View 層和 Controller 層沒有分離,邏輯比較混亂;第二,同樣因?yàn)?View 和 Controller 層的耦合,導(dǎo)致 Activity 或者 Fragment 很臃腫,代碼量很大。由于本例比較簡單,所以這兩個(gè)問題都不是很明顯,如果 Activity 中的業(yè)務(wù)量很大,那么問題就會(huì)體現(xiàn)出來,開發(fā)和維護(hù)的成本會(huì)很高。

如何使用 MVP

既然 MVC 有這些問題,那么應(yīng)該如何改進(jìn)呢,答案就是使用 MVP 的架構(gòu),關(guān)于 MVP 架構(gòu)的定義前面已經(jīng)說了,下面看一下它的模型圖

mvp

這個(gè)圖也很簡單,當(dāng) View 需要更新數(shù)據(jù)時(shí),首先去找 Presenter,然后 Presenter 去找 Model 請求數(shù)據(jù),Model 獲取到數(shù)據(jù)之后通知 Presenter,Presenter 再通知 View 更新數(shù)據(jù),這樣 Model 和 View 就不會(huì)直接交互了,所有的交互都由 Presenter 進(jìn)行,Presenter 充當(dāng)了橋梁的角色。很顯然,Presenter 必須同時(shí)持有 View 和 Model 的對象的引用,才能在它們之間進(jìn)行通信。

接下來用 MVP 的架構(gòu)來改造上面的例子,代碼如下

interface MVPView {
    void updateTv(String text);
}


public class MVPActivity extends AppCompatActivity implements MVPView {
    private Button button;
    private TextView textView;
    private Presenter presenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        button = findViewById(R.id.button);
        textView = findViewById(R.id.text_view);
        presenter = new Presenter(this);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.request();
            }
        });
    }

    @Override
    public void updateTv(String text) {
        textView.setText(text);
    }
}


interface Callback {
    void onResult(String text);
}


public class HttpModel {
    private Callback callback;

    public HttpModel(Callback callback) {
        this.callback = callback;
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            callback.onResult((String) msg.obj);
        }
    };

    public void request() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    Message msg = handler.obtainMessage();
                    msg.obj = "從網(wǎng)絡(luò)獲取到的數(shù)據(jù)";
                    handler.sendMessage(msg);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}


public class Presenter {
    private MVPView view;
    private HttpModel model;

    public Presenter(MVPView view) {
        this.view = view;
        model = new HttpModel(new Callback() {
            @Override
            public void onResult(String text) {
                Presenter.this.view.updateTv(text);
            }
        });
    }

    public void request() {
        model.request();
    }
}

簡單解釋一下上面的代碼,首先創(chuàng)建一個(gè) MVPView 的接口,它即時(shí) View 層,里面有一個(gè)更新 TextView 的方法,然后讓 Activity 實(shí)現(xiàn)這個(gè)接口,并復(fù)寫更新 TextView 的方法。Model 層不再傳入 TextView 了,而是傳入一個(gè)回調(diào)接口 Callback,因?yàn)榫W(wǎng)絡(luò)請求獲取數(shù)據(jù)是異步的,在獲取到數(shù)據(jù)之后需要通過 Callback 來通知 Presenter。Presenter 也很簡單,首先在它的構(gòu)造方法中,同時(shí)持有 View 和 Model 的引用,再對外提供一個(gè) request 方法。

分析一下上面代碼執(zhí)行的流程,當(dāng)點(diǎn)擊 Button 的時(shí)候,Presenter 調(diào)用 request 方法,在它的內(nèi)部,通過 Model 調(diào)用 request 方法來請求數(shù)據(jù),請求到數(shù)據(jù)之后,切換到主線程,調(diào)用 callback 的 onResult 方法來通知 Presenter,這時(shí)候 Presenter 就會(huì)調(diào)用 View 的 updateTv 方法來更新 TextView,完成了整個(gè)流程,可以發(fā)現(xiàn),在整個(gè)過程從,View 和 Model 并沒有直接交互,所有的交互都是在 Presenter 中進(jìn)行的。

注意事項(xiàng)

接口的必要性

可能有的同學(xué)會(huì)問,為什么要寫一個(gè) MVPView 的接口,直接把 Activity 本身傳入到 Presenter 不行嗎?這當(dāng)然是可行的,這里使用接口主要是為了代碼的復(fù)用,試想一下,如果直接傳入 Activity,那么這個(gè) Presenter 就只能為這一個(gè) Activity 服務(wù)。舉個(gè)例子,假設(shè)有個(gè) App 已經(jīng)開發(fā)完成了,可以在手機(jī)上正常使用,現(xiàn)在要求做平板上的適配,在平板上的界面顯示效果有所變化,TextView 并不是直接在 Activity 中的,而是在 Fragment 里面,如果沒有使用 View 的接口的話,那就需要再寫一個(gè)針對 Fragment 的 Presenter,然后把整個(gè)過程再來一遍。但是使用 View 的接口就很簡單了,直接讓 Fragment 實(shí)現(xiàn)這個(gè)接口,然后復(fù)寫接口里面的方法,Presenter 和 Model 層都不需要做任何改動(dòng)。同理,Model 層也可以采用接口的方式來寫。

防止內(nèi)存泄漏

其實(shí)上面的代碼存在內(nèi)存泄漏的風(fēng)險(xiǎn)。試想一下,如果在點(diǎn)擊 Button 之后,Model 獲取到數(shù)據(jù)之前,退出了 Activity,此時(shí)由于 Activity 被 Presenter 引用,而 Presenter 正在進(jìn)行耗時(shí)操作,會(huì)導(dǎo)致 Activity 的對象無法被回收,造成了內(nèi)存泄漏,解決的方式很簡單,在 Activity 退出的時(shí)候,把 Presenter 對中 View 的引用置為空即可。

// Presenter.java
public void detachView() {
    view = null;
}

// MVPActivity.java
@Override
protected void onDestroy() {
    super.onDestroy();
    presenter.detachView();
}

另外還有一個(gè)問題,雖然這里 Activity 不會(huì)內(nèi)存泄漏了,但是當(dāng) Activity 退出之后,Model 中請求數(shù)據(jù)就沒有意義了,所以還應(yīng)該在 detachView 方法中,把 Handler 的任務(wù)取消,避免造成資源浪費(fèi),這個(gè)比較簡單,就不貼代碼了。

MVP 的封裝

很顯然,MVP 的實(shí)現(xiàn)套路是大致相同的,如果在一個(gè)應(yīng)用中,存在大量的 Activity 和 Fragment,并且都使用 MVP 的架構(gòu),那么難免會(huì)有很多重復(fù)工作,所以封裝就很有必要性了。

在說 MVP 的封裝之前,需要強(qiáng)調(diào)一點(diǎn),MVP 更多的是一種思想,而不是一種模式,每個(gè)開發(fā)者都可以按照自己的思路來實(shí)現(xiàn)具有個(gè)性化的 MVP,所以不同的人寫出的 MVP 可能會(huì)有一些差別,筆者在此僅提供一種實(shí)現(xiàn)思路,供讀者參考。

首先 Model、View 和 Presenter 都可能會(huì)有一些通用性的操作,所以可以分別定義三個(gè)對應(yīng)的底層接口。

interface BaseModel {
}

interface BaseView {
    void showError(String msg);
}

public abstract class BasePresenter<V extends BaseView, M extends BaseModel> {
    protected V view;
    protected M model;

    public BasePresenter() {
        model = createModel();
    }

    void attachView(V view) {
        this.view = view;
    }

    void detachView() {
        this.view = null;
    }

    abstract M createModel();
}

這里的 View 層添加了一個(gè)通用的方法,顯示錯(cuò)誤信息,寫在接口層,可以在實(shí)現(xiàn)處按照需求來顯示,比如有的地方可能會(huì)是彈出一個(gè) Toast,或者有的地方需要將錯(cuò)誤信息顯示在 TextView 中,Model 層也可以根據(jù)需要添加通用的方法,重點(diǎn)來看一下 Presenter 層。

這里的 BasePresenter 采用了泛型,為什么要這么做呢?主要是因?yàn)?Presenter 必須同時(shí)持有 View 和 Model 的引用,但是在底層接口中無法確定他們的類型,只能確定他們是 BaseView 和 BaseModel 的子類,所以采用泛型的方式來引用,就巧妙的解決了這個(gè)問題,在 BasePresenter 的子類中只要定義好 View 和 Model 的類型,就會(huì)自動(dòng)引用他們的對象了。Presenter 中的通用的方法主要就是 attachView 和 detachView,分別用于創(chuàng)建 View 對象和把 View 的對象置位空,前面已經(jīng)說過,置空是為了防止內(nèi)存泄漏,Model 的對象可以在 Presenter 的構(gòu)造方法中創(chuàng)建。另外,這里的 Presenter 也可以寫成接口的形式,讀者可以按照自己的喜好來選擇。

然后看一下在業(yè)務(wù)代碼中該如何使用 MVP 的封裝,代碼如下

interface TestContract {

    interface Model extends BaseModel {
        void getData1(Callback1 callback1);
        void getData2(Callback2 callback2);
        void getData3(Callback3 callback3);
    }

    interface View extends BaseView {
        void updateUI1();
        void updateUI2();
        void updateUI3();
    }

    abstract class Presenter extends BasePresenter<View, Model> {
        abstract void request1();
        abstract void request2();
        void request3() {
            model.getData3(new Callback3() {
                @Override
                public void onResult(String text) {
                    view.updateUI3();
                }
            });
        }
    }
}

首先定義一個(gè) Contract 契約接口,然后把 Model、View、和 Presenter 的子類分別放入 Contract 的內(nèi)部,這里的一個(gè) Contract 就對應(yīng)一個(gè)頁面(一個(gè) Activity 或者一個(gè) Fragment),放在 Contract 內(nèi)部是為了讓同一個(gè)頁面的邏輯方法都放在一起,方便查看和修改。Presenter 中的 request3 方法演示了如何通過 Presenter 來進(jìn)行 View 和 Model 的交互。

接下來要做的就是實(shí)現(xiàn)這三個(gè)模塊的邏輯方法了,在 Activity 或 Fragment 中實(shí)現(xiàn) TextContract.View 的接口,再分別創(chuàng)建兩個(gè)類用來實(shí)現(xiàn) TextContract.Model 和 TextContract.Presenter,復(fù)寫里面的抽象方法就好了。

擴(kuò)展:用 RxJava 簡化代碼

上面的代碼中,Model 層中的每個(gè)方法都傳入了一個(gè)回調(diào)接口,這是因?yàn)楂@取數(shù)據(jù)往往是異步的,在獲取的數(shù)據(jù)時(shí)需要用回調(diào)接口通知 Presenter 來更新 View。

如果想要避免回調(diào)接口,可以采用 RxJava 的方式來 Model 獲取的數(shù)據(jù)直接返回一個(gè) Observable,接下來用 RxJava 的方式來改造前面的例子

public class HttpModel {
    public Observable<String> request() {
        return Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> emitter) throws Exception {
                Thread.sleep(2000);
                emitter.onNext("從網(wǎng)絡(luò)獲取到的數(shù)據(jù)");
                emitter.onComplete();
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }
}


public class Presenter {
    private MVPView view;
    private HttpModel model;

    public Presenter(MVPView view) {
        this.view = view;
        model = new HttpModel();
    }

    private Disposable disposable;

    public void request() {
        disposable = model.request()
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(String s) throws Exception {
                        view.updateTv(s);
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {

                    }
                });
    }

    public void detachView() {
        view = null;
        if (disposable != null && !disposable.isDisposed()) {
            disposable.dispose();
        }
    }
}

Model 的 request 方法直接返回一個(gè) Observable,然后在 Presenter 中調(diào)用 subscribe 方法來通知 View 更新,這樣就避免了使用回調(diào)接口。

開源庫推薦

最后,推薦一個(gè) MVP 架構(gòu)的開源庫,正如筆者所說,MVP 更多的是一種思想,所以 github 上關(guān)于 MVP 的開源庫并不多,大多是在完整的 APP 內(nèi)部自己封裝的 MVP。如果想要比較簡單的集成 MVP 的架構(gòu),筆者推薦這個(gè)庫
https://github.com/sockeqwe/mosby
它的使用方法比較簡單,可以直接參考官方的 demo,接下來簡單的分析一下作者的封裝思想。

首先 View 層和 Presenter 層分別有一個(gè)基礎(chǔ)的接口

public interface MvpView {
}

public interface MvpPresenter<V extends MvpView> {

  /**
   * Set or attach the view to this presenter
   */
  @UiThread
  void attachView(V view);

  /**
   * Will be called if the view has been destroyed. Typically this method will be invoked from
   * <code>Activity.detachView()</code> or <code>Fragment.onDestroyView()</code>
   */
  @UiThread
  void detachView(boolean retainInstance);
}

這里加 @UIThread 注解是為了確保 attachView 和 detachView 都運(yùn)行在主線程中。
然后業(yè)務(wù)代碼的 Activity 需要繼承 MvpActivity

public abstract class MvpActivity<V extends MvpView, P extends MvpPresenter<V>>
    extends AppCompatActivity implements MvpView,
    com.hannesdorfmann.mosby3.mvp.delegate.MvpDelegateCallback<V,P> {

  protected ActivityMvpDelegate mvpDelegate;
  protected P presenter;
  protected boolean retainInstance;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getMvpDelegate().onCreate(savedInstanceState);
  }

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

  @Override protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    getMvpDelegate().onSaveInstanceState(outState);
  }

  @Override protected void onPause() {
    super.onPause();
    getMvpDelegate().onPause();
  }

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

  @Override protected void onStart() {
    super.onStart();
    getMvpDelegate().onStart();
  }

  @Override protected void onStop() {
    super.onStop();
    getMvpDelegate().onStop();
  }

  @Override protected void onRestart() {
    super.onRestart();
    getMvpDelegate().onRestart();
  }

  @Override public void onContentChanged() {
    super.onContentChanged();
    getMvpDelegate().onContentChanged();
  }

  @Override protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    getMvpDelegate().onPostCreate(savedInstanceState);
  }

  /**
   * Instantiate a presenter instance
   *
   * @return The {@link MvpPresenter} for this view
   */
  @NonNull public abstract P createPresenter();

  /**
   * Get the mvp delegate. This is internally used for creating presenter, attaching and detaching
   * view from presenter.
   *
   * <p><b>Please note that only one instance of mvp delegate should be used per Activity
   * instance</b>.
   * </p>
   *
   * <p>
   * Only override this method if you really know what you are doing.
   * </p>
   *
   * @return {@link ActivityMvpDelegateImpl}
   */
  @NonNull protected ActivityMvpDelegate<V, P> getMvpDelegate() {
    if (mvpDelegate == null) {
      mvpDelegate = new ActivityMvpDelegateImpl(this, this, true);
    }

    return mvpDelegate;
  }

  @NonNull @Override public P getPresenter() {
    return presenter;
  }

  @Override public void setPresenter(@NonNull P presenter) {
    this.presenter = presenter;
  }

  @NonNull @Override public V getMvpView() {
    return (V) this;
  }
}

MvpActivity 中持有一個(gè) ActivityMvpDelegate 對象,它的實(shí)現(xiàn)類是 ActivityMvpDelegateImpl,并且需要傳入 MvpDelegateCallback 接口,ActivityMvpDelegateImpl 的代碼如下

public class ActivityMvpDelegateImpl<V extends MvpView, P extends MvpPresenter<V>>
    implements ActivityMvpDelegate {

  protected static final String KEY_MOSBY_VIEW_ID = "com.hannesdorfmann.mosby3.activity.mvp.id";

  public static boolean DEBUG = false;
  private static final String DEBUG_TAG = "ActivityMvpDelegateImpl";

  private MvpDelegateCallback<V, P> delegateCallback;
  protected boolean keepPresenterInstance;
  protected Activity activity;
  protected String mosbyViewId = null;

  /**
   * @param activity The Activity
   * @param delegateCallback The callback
   * @param keepPresenterInstance true, if the presenter instance should be kept across screen
   * orientation changes. Otherwise false.
   */
  public ActivityMvpDelegateImpl(@NonNull Activity activity,
      @NonNull MvpDelegateCallback<V, P> delegateCallback, boolean keepPresenterInstance) {

    if (activity == null) {
      throw new NullPointerException("Activity is null!");
    }

    if (delegateCallback == null) {
      throw new NullPointerException("MvpDelegateCallback is null!");
    }
    this.delegateCallback = delegateCallback;
    this.activity = activity;
    this.keepPresenterInstance = keepPresenterInstance;
  }

  /**
   * Determines whether or not a Presenter Instance should be kept
   *
   * @param keepPresenterInstance true, if the delegate has enabled keep
   */
  static boolean retainPresenterInstance(boolean keepPresenterInstance, Activity activity) {
    return keepPresenterInstance && (activity.isChangingConfigurations()
        || !activity.isFinishing());
  }

  /**
   * Generates the unique (mosby internal) view id and calls {@link
   * MvpDelegateCallback#createPresenter()}
   * to create a new presenter instance
   *
   * @return The new created presenter instance
   */
  private P createViewIdAndCreatePresenter() {

    P presenter = delegateCallback.createPresenter();
    if (presenter == null) {
      throw new NullPointerException(
          "Presenter returned from createPresenter() is null. Activity is " + activity);
    }
    if (keepPresenterInstance) {
      mosbyViewId = UUID.randomUUID().toString();
      PresenterManager.putPresenter(activity, mosbyViewId, presenter);
    }
    return presenter;
  }

  @Override public void onCreate(Bundle bundle) {

    P presenter = null;

    if (bundle != null && keepPresenterInstance) {

      mosbyViewId = bundle.getString(KEY_MOSBY_VIEW_ID);

      if (DEBUG) {
        Log.d(DEBUG_TAG,
            "MosbyView ID = " + mosbyViewId + " for MvpView: " + delegateCallback.getMvpView());
      }

      if (mosbyViewId != null
          && (presenter = PresenterManager.getPresenter(activity, mosbyViewId)) != null) {
        //
        // Presenter restored from cache
        //
        if (DEBUG) {
          Log.d(DEBUG_TAG,
              "Reused presenter " + presenter + " for view " + delegateCallback.getMvpView());
        }
      } else {
        //
        // No presenter found in cache, most likely caused by process death
        //
        presenter = createViewIdAndCreatePresenter();
        if (DEBUG) {
          Log.d(DEBUG_TAG, "No presenter found although view Id was here: "
              + mosbyViewId
              + ". Most likely this was caused by a process death. New Presenter created"
              + presenter
              + " for view "
              + getMvpView());
        }
      }
    } else {
      //
      // Activity starting first time, so create a new presenter
      //
      presenter = createViewIdAndCreatePresenter();
      if (DEBUG) {
        Log.d(DEBUG_TAG, "New presenter " + presenter + " for view " + getMvpView());
      }
    }

    if (presenter == null) {
      throw new IllegalStateException(
          "Oops, Presenter is null. This seems to be a Mosby internal bug. Please report this issue here: https://github.com/sockeqwe/mosby/issues");
    }

    delegateCallback.setPresenter(presenter);
    getPresenter().attachView(getMvpView());

    if (DEBUG) {
      Log.d(DEBUG_TAG, "View" + getMvpView() + " attached to Presenter " + presenter);
    }
  }

  private P getPresenter() {
    P presenter = delegateCallback.getPresenter();
    if (presenter == null) {
      throw new NullPointerException("Presenter returned from getPresenter() is null");
    }
    return presenter;
  }

  private V getMvpView() {
    V view = delegateCallback.getMvpView();
    if (view == null) {
      throw new NullPointerException("View returned from getMvpView() is null");
    }
    return view;
  }

  @Override public void onDestroy() {
    boolean retainPresenterInstance = retainPresenterInstance(keepPresenterInstance, activity);
    getPresenter().detachView(retainPresenterInstance);
    if (!retainPresenterInstance && mosbyViewId != null) {
      PresenterManager.remove(activity, mosbyViewId);
    }

    if (DEBUG) {
      if (retainPresenterInstance) {
        Log.d(DEBUG_TAG, "View"
            + getMvpView()
            + " destroyed temporarily. View detached from presenter "
            + getPresenter());
      } else {
        Log.d(DEBUG_TAG, "View"
            + getMvpView()
            + " destroyed permanently. View detached permanently from presenter "
            + getPresenter());
      }
    }
  }

  @Override public void onPause() {

  }

  @Override public void onResume() {

  }

  @Override public void onStart() {

  }

  @Override public void onStop() {

  }

  @Override public void onRestart() {

  }

  @Override public void onContentChanged() {

  }

  @Override public void onSaveInstanceState(Bundle outState) {
    if (keepPresenterInstance && outState != null) {
      outState.putString(KEY_MOSBY_VIEW_ID, mosbyViewId);
      if (DEBUG) {
        Log.d(DEBUG_TAG,
            "Saving MosbyViewId into Bundle. ViewId: " + mosbyViewId + " for view " + getMvpView());
      }
    }
  }

  @Override public void onPostCreate(Bundle savedInstanceState) {
  }
}

代碼有點(diǎn)長,但是邏輯還是比較清晰的,它其實(shí)就是在 onCreate 方法中根據(jù)不同的情況來創(chuàng)建 Presenter 對象,并通過 MvpDelegateCallback 的 setPresenter 方法把它保存在 MvpDelegateCallback 中,這里的 MvpDelegateCallback 就是 MvpActivity 本身。另外可以在 Activity 的各個(gè)生命周期方法中加入需要實(shí)現(xiàn)的邏輯。

可能有的同學(xué)會(huì)問,為什么沒有 Model 呢?其實(shí)這里的代碼主要是對 Presenter 的封裝,從作者給出的官方 demo 中可以發(fā)現(xiàn),Model 和 View 都是需要自己創(chuàng)建的。

這里只做一個(gè)簡單的分析,有興趣的同學(xué)可以自己查看它的源碼,再強(qiáng)調(diào)一遍,MVP 更多的是一種思想,不用局限于某一種套路,可以在領(lǐng)悟了它的思想之后,寫出自己的 MVP。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,326評論 25 708
  • 作者:李旺成 時(shí)間:2016年4月3日 “Android MVP 詳解(下)”已經(jīng)發(fā)布,歡迎大家提建議。 MVP ...
    diygreen閱讀 129,363評論 85 1,320
  • 轉(zhuǎn)載至:http://m.itdecent.cn/p/9a6845b26856 “Android MVP 詳解...
    SnowDragonYY閱讀 10,421評論 5 241
  • 10月1號,我們在去阿拉善英雄會(huì)現(xiàn)場的路中午我們吃的零食,下午我們到達(dá)磧口鎮(zhèn),晚上睡覺的時(shí)候在磧口客棧里睡的,磧口...
    沈祺鴻閱讀 603評論 0 2
  • 2014,1月。距離高考還有5個(gè)多月,我在緊張的備考當(dāng)中,漫天飛舞的試卷,老師苦口婆心的叮嚀囑咐卻依舊沒能抵擋得過...
    咚_閱讀 459評論 0 0

友情鏈接更多精彩內(nèi)容