### **參考**:
原文出處------>[Model-View-Presenter (MVP)](http://hannesdorfmann.com/android/mosby),GitHub地址[Mosby](https://github.com/sockeqwe/mosby),[GitHub](https://github.com/sockeqwe)
譯文如下:
譯文出處------>[MVP(Model-View-Presenter)模式](http://wiki.jikexueyuan.com/project/notes/Android-Java/MVP.html)
[用于android的Model-View-Presenter庫](http://hannesdorfmann.com/mosby/)
**注意:這里僅供參考,具體問題具體分析**
### **MVP(Model-View-Presenter)模式**
[工作流程](http://hannesdorfmann.com/android/mosby/)

* View通常會持有Presenter的引用;
* Presenter持有View和Model的引用;
* Model應該包括數據和對數據的獲取或者修改操作;
#### **具體來說**

**invoke:調用,query:查詢**
* MVP之間應該盡量解耦,可以通過定義接口來實現,相互持有的是接口引用;
* View應該盡可能聽從Presenter的指令,而不是自己控制,如:接受Presenter的showLoading指令之后才顯示正在加載;
* 與用戶的交互由View負責,顯示的細節由View負責;
### **MVP in Android(Mosby)**
#### **使用場景**:
* 單一的UI單元,例如Fragment,或者一個ViewGroup,原則是這部分UI由一個Presenter獨立負責,不必相互干擾;
* Acticity、Fragment更應該是View,而不是Presenter;
* Core模塊集成了Butterknife、FragmentArgs、Icepick等開源庫;
* MVP模塊,定義了MvpPresenter,MvpActivity、MvpFragment類,作為presenter,view的基類;
* MvpPresenter是一個接口,有一個MvpBasePresenter實現,使用WeakReference保存View的引用,所以Presenter(子類)要調用View的接口時,需要調用isViewAttached()檢查view是否有效,調用getView()獲取其引用;(防止內存泄漏)
* MvpLceView,模板化loading-content-error流程;
* ViewState模塊:保存View(并非Android中的View,這里是指MVP中View視圖)的狀態,處理屏幕旋轉、View(Fragment、Activity)重新創建時的狀態恢復;
* 還有針對Dagger、Retrofit、RX、Testing等的模塊;
#### [**一些建議**](http://hannesdorfmann.com/android/mosby-playbook/)
* Don't “over-architect”
In fact refactoring is a vital part of development, you simply can’t avoid it (but you can try to minimize the number of refactoring). Start with doing the simple thing and afterwards refactor and make it more extensible as app and requirements grow.
譯文:事實上,重構是開發的一個重要部分,你根本無法避免它(但是你可以盡量減少重構的次數)。從做簡單的事情開始,然后重新進行重構,隨著應用程序和需求的增長,使其更具擴展性。
* Use inheritance wisely(正確利用繼承)
正確利用繼承,基類擁有基本特點,子類增加新特性;避免“子類爆炸”;
* Don't see MVP as an MVC variant(不要把MVP看成MVC的轉變)

Controller負責控制View(UI)和用戶交互時應該執行的動作(調用Presenter的哪個方法);當被Presenter調用顯示方法時,如何顯示(動效、數據使用方式);
- Take separation of Model, View and Presenter serious(嚴肅的將Model, View and Presenter分離)
寫代碼的時候,認真思考每一行代碼的功能應該屬于哪個模塊,目前的位置是否合適?
- View負責UI顯示,以及對UI、用戶的響應
- Model負責數據獲取、存儲、處理
- Presenter負責配合/連接兩者
- Presentation Model
- 當UI顯示對于Model的需求與數據源不一致時:最好的辦法是加一層wrapper(包裝,裝飾);其次是為Model加一些方法,產生需要的域;最差的是為Model加上這些域;
- Navigation is a UI thing(其他的一些UI邏輯需要view負責,而不是通過Presenter)
不同界面之間的跳轉應該由view負責
- onActivityResult() and MVP
當result僅僅用于顯示時,無需涉及presenter;當需要額外處理時,例如圖片處理、上傳等,則需要放到presenter中去;
- MVP and EventBus - A match made in heaven(完美搭配)
EventBus常被用于業務邏輯事件、UI更新事件(Fragment之間通信)
- 前者presenter負責接收event,控制view顯示;
- 后者有一種常見做法:Activity實現一個listener接口,讓fragment持有該接口引用(attach時賦值),使用該接口進行通信;然而使用EventBus更合適,解耦更徹底;
- Optimistic propagation
用戶的操作,首先給其成功的反饋,然后在后臺進行處理,失敗后給出失敗的提示,并撤銷成功的反饋(顯示),這種做法對于用戶體驗或許更佳;
如何看待內存泄漏:避免activity/fragment的泄漏,但對于presenter,如果希望操作執行完之后再被GC,則subscriber/runnable持有的presenter引用,可以認為是合理的;
- MVP scales
MVP的V可以是整個屏幕顯示的內容,也可以是屏幕上的某個模塊顯示的內容;
- Not every View needs MVP(并不是所有的View視圖都要用到MVP模式)
靜態的頁面并不需要MVP
- Only display one Model per MVP view
一個V顯示多個M會為代碼增加復雜性,尤其是當需要保存ViewState時;合理的做法是將V拆分為獨立的V,每個V只負責顯示一個M;V可以是fragment,也可以是View/ViewGroup;例子: 
- Android Services
**Service顯然屬于業務邏輯部分,由presenter與之通信是合理的**;
- Use LCE only if you have LCE Views(Loading-Content-Error (LCE)加載內容錯誤)
LCE還包含loadData,setData,如果沒有這兩個語義,就不要使用LceView,因為對接口的空實現,有違接口的規范;
- Writing custom ViewStates
當需要保存View的狀態時,定義好ViewState,并在View內維護、檢查;
- Testing custom ViewState
View和ViewState都是純Java代碼,可以使用Java的單元測試方法進行測試;
- ViewState variants
有可能View既顯示了數據,又在進行刷新操作,但是ViewState始終只會處于一個狀態,通常的做法是為原來的狀態再加一層修飾,加以區分;也可以定義一個新的ViewState;
- Not every UI representation is a ViewState
例如:顯示空內容,并不是新狀態,而是show content,只不過content為空;
- Not every View needs a ViewState
mosby的ViewState通過SavedInstance保存,限制大小為1MB;另外保存的數據有可能是過時的,需要注意;
- Avoid Fragments on the backstack and child fragments
Fragment的生命周期中可能會有一些問題,所以盡量避免將Fragment放入back stack(返回棧),或者子Fragment;
- Mosby supports MVP for ViewGroups
- Mosby provides Delegates
好處:
- 可以把MVP模式應用到mosby未包含的類中,例如DialogFragment;甚至是第三方庫;
- 實現自定義的Delegate,可以改變默認Delegate的行為;
#### **關于Mosby的一些補充**
**核心模塊**
- 兩個基類:MosbyActivity和MosbyFragment。
- 它們是所有其他Activity或Fragment子類的基本類(基礎)。
- 兩者都使用眾所周知的注釋處理器([annotation processors](http://hannesdorfmann.com/annotation-processing/annotationprocessing101))來減少寫入樣板代碼。
- MosbyActivity和MosbyFragment使用[Butterknife](http://jakewharton.github.io/butterknife/)進行視圖“注入”,[Icepick](https://github.com/frankiesardo/icepick)用于保存和恢復實例狀態到一個bundle和FragmentArgs用于注入片段參數。你不必像`Butterknife.inject(this)`(Butterknife版本不一樣,調用方法可能也不一樣)那樣調用注入方法。這種代碼已經包含在MosbyActivity和MosbyFragment中。它只是開箱即用。你唯一需要做的就是在你的子類中使用相應的注釋。
- 該核心-模塊是**不相關的MVP**。這**只是建立摩天大樓應用程序的基礎**。
**MVP - Module**
- Mosby的MVP模塊**使用泛型來確保類型安全**。所有視圖的基類是MvpView。基本上它只是一個空的界面。Presenter的基類是MvpPresenter
~~~
public interface MvpView { }
public interface MvpPresenter<V extends MvpView> {
public void attachView(V view);
public void detachView(boolean retainInstance);
}
~~~
- MvpActivity和MvpFragment,它們是MvpViews作為活動和碎片的基類:
**MvpActivity.java**
~~~
public abstract class MvpActivity<V extends MvpView, P extends MvpPresenter> extends MosbyActivity implements MvpView {
protected P presenter;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = createPresenter();
presenter.attachView(this);
}
@Override protected void onDestroy() {
super.onDestroy();
presenter.detachView(false);
}
protected abstract P createPresenter();
}
~~~
**MvpFragment.java**
~~~
public abstract class MvpFragment<V extends MvpView, P extends MvpPresenter> extends MosbyFragment implements MvpView {
protected P presenter;
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Create the presenter if needed
if (presenter == null) {
presenter = createPresenter();
}
presenter.attachView(this);
}
@Override public void onDestroyView() {
super.onDestroyView();
presenter.detachView(getRetainInstance());
}
protected abstract P createPresenter();
}
~~~
注意:
- 目的:MvpView(即片段或活動)被附加到和分離于MvpPresenter。Mosby需要使用Activities和Fragments生命周期來做到這一點,就像你在上面所看到的代碼中看到的一樣。通常情況下,Presenter演示者將被綁定到該生命周期。所以在`presenter.onAttach()`和`presenter.onDetach()`中應該完成初始化和清理工作(比如取消異步運行任務)。稍后我們將討論Presenter演示者如何通過使用`setRetainInstanceState(true)`在Fragments片段中“逃避”這個生命周期。
- MvpPresenter是一個接口。MVP模塊提供MvpBasePresenter,這是一個Presenter的實現類,它使用了WeakReference保存被引用的視圖(Fragment or Activity),以避免內存泄漏。因此,當演示者想要調用視圖的方法時,您必須通過檢查`isViewAttached()`并使用`getView()`獲取引用來檢查View視圖是否附加到Presenter。
**Loading-Content-Error (LCE)加載內容錯誤**
通常Fragment一遍又一遍地做同樣的事情。比如:它在后臺加載數據,在加載時顯示加載視圖(即ProgressBar),在屏幕上顯示加載的數據或在加載失敗時顯示錯誤視圖。現在支持拉刷新很容易,因為SwipeRefreshLayout是Android的支持庫的一部分。為了**不重復實施這個工作流程**,Mosby的MVP模塊提供了MvpLceView:
~~~
public interface MvpLceView<M> extends MvpView {
/**
* Display a loading view while loading data in background.
* <b>The loading view must have the id = R.id.loadingView</b>
*
* @param pullToRefresh true, if pull-to-refresh has been invoked loading.
*/
public void showLoading(boolean pullToRefresh);
/**
* Show the content view.
*
* <b>The content view must have the id = R.id.contentView</b>
*/
public void showContent();
/**
* Show the error view.
* <b>The error view must be a TextView with the id = R.id.errorView</b>
*
* @param e The Throwable that has caused this error
* @param pullToRefresh true, if the exception was thrown during pull-to-refresh, otherwise
* false.
*/
public void showError(Throwable e, boolean pullToRefresh);
/**
* The data that should be displayed with {@link #showContent()}
*/
public void setData(M data);
}
~~~
可以使用**MvpLceActivity**實現MvpLceView和**MvpLceFragment**實現MvpLceView的那種視圖。兩者都假設填充的xml布局包含視圖與`R.id.loadingView`,`R.id.contentView`和`R.id.errorView`。
**示例**:
我們通過使用CountriesAsyncLoader加載Country列表,并將其顯示在RecyclerView的Fragment中。你可以在[這里](https://db.tt/ycrCwt1L)下載[示例APK](https://db.tt/ycrCwt1L)。源碼托管在[GitHub上](https://github.com/sockeqwe/mosby/tree/master/sample)
注意的是下面示例中的代碼可能與[GitHub上的代碼](https://github.com/Alexwsc/mosby/tree/master/sample)不一樣,這是因為該文章是作者之前就寫過的,里面的依賴庫可能已經更新,而且作者寫的開源庫也在迭代,所以一切以GitHub上的源碼為準
首先定義視圖界面CountriesView
~~~
public interface CountriesView extends MvpLceView<List<Country>> {
}
~~~
**為什么我需要為View定義接口?**
1. 由于它是一個接口,你可以改變View視圖的實現。你可以簡單的將你的代碼從擴展Activity的東西移到擴展Fragment的東西上。
2. 模塊化:您可以將整個業務邏輯,Presenter和View Interface移動到獨立的庫項目中。然后,您可以在包含各種應用程序的Presenter中使用此庫。下圖顯示了左側使用一個Activity的[kicker APP](https://play.google.com/store/apps/details?id=com.netbiscuits.kicker),而[meinVerein app](https://play.google.com/store/apps/details?id=com.tickaroo.meinverein)使用了嵌入在ViewPager中的Fragment。兩者都使用相同的庫,其中View-Interface和Presenter被定義和單元測試。

3. 您可以輕松編寫單元測試,因為您可以通過實現視圖界面來模擬視圖。也可以引入演示者的java接口,通過使用模擬演示者對象進行單元測試更加容易。
4. 為視圖定義一個接口的另一個非常好的副作用是,你不必直接從演示者Presenter調用activity / fragment(活動/片段)的方法。由于在實現演示者的過程中,您在IDE的自動完成中看到的唯一方法就是視圖界面的那些方法,因此您將得到**明確的分離**。從我個人的經歷來看,我可以說這是非常有用的,特別是如果你在一個團隊工作。
請注意,我們也可以使用`MvpLceView <List <Country >>`而不是定義一個(空的,因為繼承方法)接口CountriesView。但是有一個專用的接口界面CountriesView可以提高代碼的可讀性,而且我們可以更靈活地在將來定義更多的與View有關的方法。
下面是代碼
**countries_list.xml**
~~~
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.hannesdorfmann.mosby3.sample.mvp.lce.activity.CountriesActivity">
<include layout="@layout/loading_view" />
<include layout="@layout/error_view" />
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
~~~
**loading_view.xml**
~~~
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/loadingView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
/>
</merge>
~~~
**error_view.xml**
~~~
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/errorView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:layout_gravity="center"
android:drawableTop="@drawable/ic_cloud_off"
android:drawablePadding="16dp"
android:textSize="20sp"
android:textColor="#656565"
android:text="An error has occurred! click here to retry"
android:gravity="center"
/>
</merge>
~~~
**CountriesPresenter.java**(控制CountriesView并啟動CountriesAsyncLoader)
~~~
public class CountriesPresenter extends MvpBasePresenter<CountriesView> {
@Override
public void loadCountries(final boolean pullToRefresh) {
getView().showLoading(pullToRefresh);
CountriesAsyncLoader countriesLoader = new CountriesAsyncLoader(
new CountriesAsyncLoader.CountriesLoaderListener() {
@Override public void onSuccess(List<Country> countries) {
if (isViewAttached()) {
getView().setData(countries);
getView().showContent();
}
}
@Override public void onError(Exception e) {
if (isViewAttached()) {
getView().showError(e, pullToRefresh);
}
}
});
countriesLoader.execute();
}
}
~~~
**CountriesFragment.java**(實現CountriesView)
~~~
public class CountriesFragment
extends MvpLceFragment<SwipeRefreshLayout, List<Country>, CountriesView, CountriesPresenter>
implements CountriesView, SwipeRefreshLayout.OnRefreshListener {
@InjectView(R.id.recyclerView) RecyclerView recyclerView;
CountriesAdapter adapter;
@Override public void onViewCreated(View view, @Nullable Bundle savedInstance) {
super.onViewCreated(view, savedInstance);
// Setup contentView == SwipeRefreshView
contentView.setOnRefreshListener(this);
// Setup recycler view
adapter = new CountriesAdapter(getActivity());
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(adapter);
loadData(false);
}
public void loadData(boolean pullToRefresh) {
presenter.loadCountries(pullToRefresh);
}
@Override protected CountriesPresenter createPresenter() {
return new SimpleCountriesPresenter();
}
// Just a shorthand that will be called in onCreateView()
@Override protected int getLayoutRes() {
return R.layout.countries_list;
}
@Override public void setData(List<Country> data) {
adapter.setCountries(data);
adapter.notifyDataSetChanged();
}
@Override public void onRefresh() {
loadData(true);
}
}
~~~
沒有太多的代碼要寫,對吧?這是因為基類 MvpLceFragment已經實現了從加載視圖切換到內容視圖或錯誤視圖。乍一看,MvpLceFragment的泛型參數列表` MvpLceFragment<SwipeRefreshLayout, List<Country>, CountriesView, CountriesPresenter>`可能會阻止你。讓我解釋一下:第一個泛型參數是內容視圖的類型。第二個是用這個片段顯示的模型。第三個是View界面,最后一個是Presenter的類型。總結一下:**MvpLceFragment <AndroidView,Model,View,Presenter>**。
另一件您可能已經注意到的是**getLayoutRes()**,它是在MosbyFragment中引入的用于擴充 xml視圖布局的簡寫:
~~~
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
return inflater.inflate(getLayoutRes(), container, false);
}
~~~
所以,而不是覆蓋**onCreateView()**你**可以重寫getLayoutRes()**。
一般來說,**onCreateView()應該只創建視圖,而onViewCreated()應該被重寫來初始化就像Adapter for RecyclerView**。
**重要提示:不要忘記調用super.onViewCreated()**
#### **ViewState - Module**
現在你應該知道如何使用Mosby。Mosby的ViewState模塊可以幫助您解決Android開發中令人討厭的事情:處理屏幕方向更改。
問題:如果我們將設備從縱向旋轉到橫向,運行我們的國家/地區示例應用程序,并且已經顯示國家,那么會發生什么呢?
答:新的CountriesFragment被實例化和顯示應用程序啟動時進度(和負載再次國家名單),而不是顯示在各國家的名單RecyclerView(因為它是屏幕旋轉之前),如下圖所示

**Mosby介紹ViewState來解決這個問題**
我們的想法是,我們跟蹤presenter演示者調用的在附加的View上的方法。例如,presenter演示者調用view.showContent()。一旦showContent()被調用,視圖就知道它的狀態是“顯示內容”,因此View視圖將這些信息存儲到ViewState中。如果視圖取向被改變期間而受到破壞時,ViewState會被存儲在Bundle(在**Activity.onSaveInstanceState(Bundle**)或**Fragment.onSaveInstanceState(Bundle**)得到的Bundle)中;將在**Activity.onCreate(Bundle**)或**Fragment.onActivityCreated(Bundle**)中恢復。
由于并不是每一種數據(我談到的數據類型作為參數在**view.setData(**)傳遞)都可以存儲在一個Bundle,不同的ViewState實現提供像ArrayListLceViewState類型的數據`ArrayList <Parcelable>`,ParcelableDataLceViewState——像 用于Parcelable或SerializeableLceViewState類型的數據——用于Serializeable類型的數據。如果你使用一個保留的碎片Fragment(更多關于保留下面的碎片),那么ViewState不會在屏幕方向變化時被破壞,也不需要保存到一個Bundle中。因此它可以存儲任何類型的數據。
在這種情況下,你應該使用**RetainingFragmentLViewViewState**。恢復ViewState很容易。由于我們有一個干凈的體系結構和View的接口,ViewState可以通過調用與演示者相同的接口方法來恢復關聯的視圖。例如**MvpLceView**基本上有3個狀態:它可以顯示showContent(),showLoading()和showError(),因此ViewState自己調用相應的方法來恢復視圖狀態。
這只是內部。你只需要知道如果你想編寫自己的自定義ViewStates。使用ViewStates非常簡單。實際上,要將**MvpLceFragment**遷移到**MvpLceViewStateFragment**,只需另外實現**createViewState(**)和**getData(**)。讓我們為**CountriesFragmen**t做:
~~~
public class CountriesFragment
extends MvpLceViewStateFragment<SwipeRefreshLayout, List<Country>, CountriesView, CountriesPresenter>
implements CountriesView, SwipeRefreshLayout.OnRefreshListener {
@InjectView(R.id.recyclerView) RecyclerView recyclerView;
CountriesAdapter adapter;
@Override public LceViewState<List<Country>, CountriesView> createViewState() {
return new RetainingFragmentLceViewState<List<Country>, CountriesView>(this);
}
@Override public List<Country> getData() {
return adapter == null ? null : adapter.getCountries();
}
// The code below is the same as before
@Override public void onViewCreated(View view, @Nullable Bundle savedInstance) {
super.onViewCreated(view, savedInstance);
// Setup contentView == SwipeRefreshView
contentView.setOnRefreshListener(this);
// Setup recycler view
adapter = new CountriesAdapter(getActivity());
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(adapter);
loadData(false);
}
public void loadData(boolean pullToRefresh) {
presenter.loadCountries(pullToRefresh);
}
@Override protected CountriesPresenter createPresenter() {
return new SimpleCountriesPresenter();
}
// Just a shorthand that will be called in onCreateView()
@Override protected int getLayoutRes() {
return R.layout.countries_list;
}
@Override public void setData(List<Country> data) {
adapter.setCountries(data);
adapter.notifyDataSetChanged();
}
@Override public void onRefresh() {
loadData(true);
}
}
~~~
就這樣。您不必更改演示者的代碼或其他內容。下圖就是改變代碼后的示意圖,可以看到現在視圖在方向更改后仍處于相同的“狀態”,即視圖顯示縱向國家列表,然后顯示國家/地區列表景觀。該視圖顯示橫向刷新指示器,并顯示更改為縱向后刷新指示器。

**編寫自己的ViewState**
ViewState是一個非常強大和靈活的概念。到目前為止,您已經學會了使用提供的LCE(加載內容錯誤)ViewsStates中的一個是多么容易。現在讓我們寫自己的自定義視圖和ViewState。我們認為應該只顯示兩個不同種類的數據對象的A和B。結果應該是這樣的:

View接口和數據對象(模型)如下所示:
~~~
public class A implements Parcelable {
String name;
public A(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class B implements Parcelable {
String foo;
public B(String foo) {
this.foo = foo;
}
public String getFoo() {
return foo;
}
}
public interface MyCustomView extends MvpView {
public void showA(A a);
public void showB(B b);
}
~~~
在這個簡單的示例中,我們沒有任何業務邏輯。讓我們假設在現實世界的應用程序將有我們的業務邏輯,產生復雜的操作一個A或B。我們的presenter 看起來像這樣:
~~~
public class MyCustomPresenter extends MvpBasePresenter<MyCustomView> {
Random random = new Random();
public void doA() {
A a = new A("My name is A "+random.nextInt(10));
if (isViewAttached()) {
getView().showA(a);
}
}
public void doB() {
B b = new B("I am B "+random.nextInt(10));
if (isViewAttached()) {
getView().showB(b);
}
}
}
~~~
**MyCustomActivity.java**(implements MyCustomView)
~~~
public class MyCustomActivity extends MvpViewStateActivity<MyCustomView, MyCustomPresenter>
implements MyCustomView {
@InjectView(R.id.textViewA) TextView aView;
@InjectView(R.id.textViewB) TextView bView;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_custom_view);
}
@Override public RestoreableViewState createViewState() {
return new MyCustomViewState(); // Our ViewState implementation
}
// Will be called when no view state exist yet,
// which is the case the first time MyCustomActivity starts
@Override public void onNewViewStateInstance() {
presenter.doA();
}
@Override protected MyCustomPresenter createPresenter() {
return new MyCustomPresenter();
}
@Override public void showA(A a) {
MyCustomViewState vs = ((MyCustomViewState) viewState);
vs.setShowingA(true);
vs.setData(a);
aView.setText(a.getName());
aView.setVisibility(View.VISIBLE);
bView.setVisibility(View.GONE);
}
@Override public void showB(B b) {
MyCustomViewState vs = ((MyCustomViewState) viewState);
vs.setShowingA(false);
vs.setData(b);
bView.setText(b.getFoo());
aView.setVisibility(View.GONE);
bView.setVisibility(View.VISIBLE);
}
@OnClick(R.id.loadA) public void onLoadAClicked() {
presenter.doA();
}
@OnClick(R.id.loadB) public void onLoadBClicked() {
presenter.doB();
}
}
~~~
由于我們沒有LCE(Loading-Content-Error),我們沒有使用MvpLceActivity作為基類。我們使用MvpViewStateActivity作為基類,這是支持ViewState的最一般的Activity實現。基本上,我們認為簡單地顯示aView或bView。**在onNewViewStateInstance()中,我們必須指定在第一個Activity開始時要做什么,因為沒有以前的ViewState實例存在要恢復**。在showA(A a)和showB(B b)中,我們必須將顯示A或B的信息保存到ViewState中。我們差不多完成了 MyCustomViewState實現丟失:
~~~
public class MyCustomViewState implements RestoreableViewState<MyCustomView> {
private final String KEY_STATE = "MyCustomViewState-flag";
private final String KEY_DATA = "MyCustomViewState-data";
public boolean showingA = true; // if false, then show B
public Parcelable data; // Can be A or B
@Override public void saveInstanceState(Bundle out) {
out.putBoolean(KEY_STATE, showingA);
out.putParcelable(KEY_DATA, data);
}
@Override public boolean restoreInstanceState(Bundle in) {
if (in == null) {
return false;
}
showingA = in.getBoolean(KEY_STATE, true);
data = in.getParcelable(KEY_DATA);
return true;
}
@Override public void apply(MyCustomView view, boolean retained) {
if (showingA) {
view.showA((A) data);
} else {
view.showB((B) data);
}
}
/**
* @param a true if showing a, false if showing b
*/
public void setShowingA(boolean a) {
this.showingA = a;
}
public void setData(Parcelable data){
this.data = data;
}
}
~~~
正如你所看到的,我們必須保存我們的ViewState通過方法saveInstanceState(),一個從Activity.onSaveInstanceState()被調用的方法,而且恢復視圖狀態的數據通過方法restoreInstanceState(),一個被調用從Activity.onCreate()的方法 。apply()方法從activity被調用來恢復view state視圖狀態。我們通過像presenter那樣調用相同的View接口方法showA()或showB()來做到這一點。
這個外部的ViewState類將復雜度和責任從視圖狀態恢復到這個分離的類中。編寫ViewState類的單元測試比編寫Activity類更容易。
#### **如何處理后臺線程?**
Presenter通常會觀察到后臺線程。有兩種情況下演示者可以處理后臺線程取決于周圍的Activity或Fragment:
**Retaining Fragment(保留片段)**
保留片段:如果設置了Fragment.setRetainInstanceState(true),那么片段在屏幕旋轉過程中不會被破壞。只有片段的GUI( 從onCreateView()返回的android.view.View)被銷毀(一個新創建的)。這意味著所有的片段類成員變量在屏幕旋轉之后仍然存在,所以屏幕方向已經改變之后,演示者仍然在那里。在這種情況下,我們只需將主視圖中的舊視圖分開,并將新視圖添加到演示者。因此,演示者不必取消任何正在運行的后臺任務,因為視圖被重新連接。例:
1. 我們開始我們的應用程序在某一個方向。
2. 保留片段被實例化并調用onCreate(),onCreateView(),createPresenter(),還通過調用presenter.attachView()將視圖(片段本身)附加到演示者。
3. 接下來,我們將設備從縱向旋轉到橫向。
4. onDestroyView()被調用,調用presenter.detachView(true)。請注意,參數為true,通知演示者片段是保留片段(否則參數將設置為false)。因此,演示者知道他不必取消正在運行的后臺線程。
5. 應用程序現在在布景(繪制)。onCreateView()被調用,但不是 createPresenter(),因為 presenter!= null,因為presenter變量因為setRetainInstanceState(true)而在方向更改中存活。
6. 視圖被presenter.attachView()重新連接到演示者。
7. ViewState被恢復。由于沒有后臺線程被取消,所以不需要重啟后臺線程。
**Activity and NOT Retaining Fragments(Activity和不保留的Fragments)**
活動和不保留碎片:在這種情況下,工作流程非常簡單。一切都被破壞(演示者實例),因此演示者應該取消正在運行的后臺任務。例:
1. 我們在一個方向用一個非保留片段開始我們的應用程序
2. 該片段被實例化和調用的onCreate() ,onCreateView() ,createPresenter() ,并通過調用附加視圖(片段),以演示presenter.attachView() 。
3. 接下來,我們將設備從縱向旋轉到橫向。
4. onDestroyView()被調用,調用presenter.detachView(false)。演示者取消后臺任務。
5. onSaveInstanceState(Bundle)在ViewState被保存到Bundle的地方被調用。
6. 應用程序現在在布景(繪制)。一個新的Fragment被實例化并調用onCreate(),onCreateView(),createPresenter(),它創建一個新的演示者實例,并通過調用presenter.attachView()將新視圖附加到新的演示者。
7. ViewState從Bundle中恢復并恢復視圖狀態。如果ViewState是showLoading,則演示者重新啟動新后臺線程以加載數據。
總結這里是ViewState支持活動的生命周期圖:

以下是具有ViewState支持的Fragments的生命周期圖:

### **Retrofit - Module**
Mosby提供**LceRetrofitPresenter**和**LceCallback**。編寫一個演示程序以支持LCE方法**showLoading**(),**showContent**()和**showError**()可以用幾行代碼完成。
~~~
public class MembersPresenter extends LceRetrofitPresenter<MembersView, List<User>> {
private GithubApi githubApi;
public MembersPresenter(GithubApi githubApi){
this.githubApi = githubApi;
}
public void loadSquareMembers(boolean pullToRefresh){
githubApi.getMembers("square", new LceCallback(pullToRefresh));
}
}
~~~
### **Dagger - Module**
建立一個沒有依賴注入的應用程序?泰德莫斯比會踢你的屁股!**Dagger是Java中使用最多的依賴注入框架之一,并且非常受Android開發人員的歡迎**。
莫斯比支持[Dagger1](https://github.com/square/dagger)。Mosby提供了一個名為**getObjectGraph**()的方法的Injector接口。通常,你有一個應用程序范圍的模塊。要輕松地共享此模塊,您必須繼承**android.app.Application**并使其實現Injector。然后所有的Activities和Fragments都可以通過調用getObjectGraph()來訪問ObjectGraph,因為DaggerActivity和DaggerFragment也是Injector。你也可以調用plus(Module)來添加Module,通過覆寫Activity或Fragment中的getObjcetGraph()來添加模塊。我個人已經遷移到 [Dagger2](https://github.com/google/dagger),它也與Mosby合作。你可以在[Github](https://github.com/sockeqwe/mosby)上找到Dagger1和Dagger2的樣本。Dagger1示例apk可以在[這里](https://db.tt/3fVqVdAz)下載,Dagger2示例apk可以在[這里](https://db.tt/z85y4fSY)下載
### **Rx - Module**
Obwarebles ftw!現在所有的酷孩子都使用RxJava,你知道嗎?RxJava非常酷!因此,Mosby提供了**MvpLceRxPresenter**,它在內部是一個訂閱者,并且會自動處理onNext(),onCompleted()和onError()以及調用相應的LCE方法,如showLoading(),shwoContent()和showError()。它還附帶RxAndroid到**observerOn**()Android的主UI線程。您可能認為您不再需要使用RxJava模型視圖展示器。那么,這是你的決定。在我看來,View和Model之間的清晰分離是非常重要的。我也認為像ViewState這樣的一些不錯的功能在沒有MVP的情況下并不容易實現。最后但并非最不重要的一點,你是否真的想返回在含有超過1000多行代碼的活動和片段中?歡迎回到意大利面代碼地獄。好吧,讓我們公平一點,這不是意大利面代碼,因為Observables引入了一個很好的結構化工作流程,但是更接近于將Activity或Fragment設置為[BLOB](http://www.antipatterns.com/briefing/sld024.htm)
### **Testing - Module(測試 - 模塊)**
您可能已經注意到存在測試模塊。該模塊在內部用于測試mosby庫。不過,它也可以用于你自己的應用程序。它使用Robolectric為您的LCE Presenter,Activities和Fragments提供單元測試模板。基本上,它檢查被測試的演示者是否工作正常:演示者是否調用showLoading(),showContent()和showError()。您也可以驗證setData()中的數據。所以你可以為Presenter和底層編寫一個黑盒測試。Mosby的測試模塊還提供了測試**MvpLceFragment**或**MvpLceActivity**的可能性。這是一種“精簡”UI測試。這些測試只檢查片段或活動是否正常工作,而不會崩潰,檢查xml布局是否包含所需的id,如R.id.loadingView,R.id.contentView和R.id.errorView,或者檢查loadView是否可見,加載數據時,錯誤視圖是否可見,內容視圖是否可以處理由setData()提交的已加載數據。這不像你用Espresso做的UI測試。我沒有看到需要為LCE Views編寫UI測試。總結這里是泰德莫斯比的測試技巧:
1. 編寫傳統的單元測試來測試您的業務邏輯和模型。
2. 使用MvpLcePresenterTest測試您的演示者,
3. 使用MvpLceFragmentTest和MvpLceActivityTest來測試你的MvpLceFragment / Activity。
4. 如果你愿意,你可以使用Espresso來編寫UI測試。
更新:通過展示如何在Android上實現郵件客戶端的新博客文章,提供與Mosby相關的一些技巧在線:[Stinson的Mosby參考文檔](http://hannesdorfmann.com/android/mosby-playbook)