我給這篇關於Android庫的博客起的名字靈感來源於《老爸老媽浪漫史》中的建築設計師Ted Mosby。這個Mosby庫能夠幫助你們在Android上經過Model-View-Presenter模式作出一個完善穩健、可重複使用的軟件,還 能夠藉助ViewState輕鬆實現屏幕翻轉。java
MVP模式是一個把view從低層模型分離出來的一種現代模式。MVP由model–view–controller (MVC)軟件模式衍生而來,經常使用於構建UIandroid
從數據庫中查詢或顯示User列表的具體流程如圖1-2:
git
以上工做流程圖應該可以說明問題了。可是,還有如下幾點值得注意的地方:github
Presenter不是一個OnClickListener。View主要是負責處理用戶輸入並調用presenter相應的方法。那麼問題來了,爲何不把Presenter 直接作成一個OnClickListener,從而把「轉發流程」給省略掉呢?你們想一想,若是這樣作的話,首先,presenter須要知道view的內部構件。舉個例子,若是一個View有兩個按鈕,且這個view在這兩個按鈕上都把Presenter 註冊成OnClickListener的 話,那麼發生點擊事件時Presenter (在不知道view中按鈕引用等內部構件的狀況下)怎麼可以區分出是哪個按鈕被點擊了 呢?Model,View和Presenter三者應解耦。其次,若是讓Presenter 執行OnClickListener,Presenter就 被綁定到了Android平臺上。理論上來講presenter和業務邏輯層都是純舊式的可以與桌面應用或其餘任何java應用共享的java代碼。數據庫
你們在第1步和第2步中能夠看到,View 只執行Presenter 指 示的操做:用戶點擊「load user button」(第1步)後,view並無直接顯示加載動畫,而是在第2步presenter明確告訴其顯示加載動畫後才顯示的。這一Model- View-Presenter的變體稱之爲MVP 被動視圖。這個view能夠說是要多笨有多笨。這時咱們須要讓presenter以一種更抽象的方式來控制view。好比,presenter在調用 view.showLoading() 時並不控制view的諸如動畫等具體事項。因此presenter不該調用view.startAnimation() 等方法。安全
經過執行MVP被動視圖,併發性以及多線程更容易處理。你們能夠看到,第3步中數據庫查詢異步運行,而且presenter做爲Listener/Observer,在數據準備顯示時presenter收到通知。服務器
目前爲止一切順利。可是你們怎麼樣把MVP運用到本身的Android 應用上呢?第一個問題在於,咱們要把MVP模式運用到什麼地方?Activity上、Fragment上、仍是像RelativeLayout這類的 ViewGroup上?咱們來看看Android平板上的Gmail應用,如圖1-3:多線程
在我看來,上圖屏幕中有四個可使用MVP的地方。我所說的「可使用MVP的地方」是指屏幕上顯示的、在邏輯上屬於一個總體的UI元素。所以這些地方也能夠稱爲是能夠運用MVP的一個單獨的UI單元。如圖 1-4.架構
看起來MVP彷佛很適合運用到Activity,特別是Fragment上。一般Fragment只負責顯示單一的如ListView之類的內容,就像依靠MailProvider 來獲取一系列Mails的InboxPresenter 控制下的 InboxView同樣。可是,MVP不 僅僅限於Fragment或Activity,它還能夠運用到SearchView中顯示的ViewGroup中。在個人大多數app裏面我都在 Fragment運用MVP模式。可是你們能夠自行決定把MVP運用到什麼地方,前提是view是獨立的,這樣這樣presenter才能在不與其餘 Presenter衝突的狀況下控制View。併發
咱們如何在不使用MVP模式時顯示Email列表到Fragment? 一般,咱們須要獲取而且合併本地SQL數據庫和從IMAP郵件服務器獲取的郵件列表,而後將郵件列表綁定到收件箱view中。那麼,此時fragment的代碼又會是怎麼樣的呢?咱們須要運行兩個AsyncTasks 並 實現一個「等待機制」(等到兩個任務將二者的加載數據合併到一個單獨的mail列表)。咱們還須要注意的是在加載時要顯示加載動畫 (ProgressBar),以後用ListView替代。咱們須要把全部的代碼放到Fragment中嗎?要是加載過程當中出現錯誤怎麼辦?屏幕翻轉怎麼 辦?誰來負責撤銷AsyncTasks ?這一系列的問題均可以經過MVP獲得解決。讓咱們跟那些帶有上千行大雜燴代碼的activity和fragment說拜拜吧
可是,在咱們深刻研究如何將MVP運用到Android中以前,咱們須要弄清楚的一個問題是:Activity或Fragment到底是一個View仍是一個Presenter。Activity或Fragment彷佛既是View也是Presenter,由於它們都有 onCreate() 或onDestroy()之 類的生命週期回調功能,而且它們負責從一個UI控件到另外一個UI 控件的轉換(好比在加載時顯示ProgressBar,而後顯示帶有數據的ListView)等View操做。你們可能會以爲這裏的Activity或 Fragment就是一個Controller,我猜可能也是這麼一個初衷。可是在經歷了幾年的Android應用開發以後,我得出這麼一個結論:咱們應 該把Activity或Fragment看做是一個不太智能的View,而不是把它們看做一個Presenter。後文我會給出緣由。
綜上,我想給你們介紹一個在Android平臺上開發基於MVP的應用的一個 Mosby庫。
Mosby
你們能夠在Github和Maven Central上找到Mosby庫。Mosby分爲幾個子模塊,你們能夠根據本身的須要選取組件。咱們來回顧一下最重要的一個模塊。
《老爸老媽浪漫史》中的建築設計師Ted Mosby想建造一棟摩天大樓。而建造這樣一棟宏偉的建築必須打好堅實的地基。這對Android應用的開發來講是也是同樣的道理。基本上,Core Module 分爲兩種類型:MosbyActivity 和MosbyFragment。這二者是全部其餘activity或fragment子類的基類(至關於建築的地基)。二者都使用咱們你們所熟知的APT (Annotation Processing Tool)來減小一些樣板式代碼。MosbyActivity 和MosbyFragment 使 用Butterknife進行view的注入,使用Icepick 將實例狀態保存和存儲到Bundle中,使用FragmentArgs注入 Fragment參數。咱們不須要再調用Butterknife.inject(this)等插入方法。這類代碼已經包含在了MosbyActivity 和 MosbyFragment中。它是即時可用的。咱們須要作的就是使用子類中相應的註解。核心模塊與MVP沒有關聯,它只是寫一個大型軟件的基礎。
Mosby庫中的MVP模塊使用泛型來確保類型安全。全部view的基類是MvpView。從根本上說這只是一個空的interface 。Presenter的基類是MvpPresenter:
public interface MvpView{} public interface MvpPresenter<V extends MvpView>{ public void attachView(V view); public void detachView(boolean retainInstance); }
上文提到,咱們把Activity和Fragment看作View。所以Mosby庫的MVP模塊提供了 屬於MvpViews 的MvpActivity和MvpFragment做爲Activity和Fragment的基類。
public abstract class MvpActivity<P extends MvpPresenter> extends MosbyActivity implements MvpView{ protected P presenter; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); presenter = createPresenter(); presenter.attachView(this); super.onDestroy(); presenter.detachView(false); } protected abstract PcreatePresenter(); } public abstract class MvpFragment<P extends MvpPresenter> MosbyFragment implements MvpView{ protected Ppresenter; @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 PcreatePresenter(); } } @Override protected void onDestroy(){
這一理念主要是一個MvpView (也就是Fragment or Activity)會關聯一個MvpPresenter,而且管理MbpPresenter的聲明週期。你們從上面的代碼片斷能夠看到,Mosby使用Activity和Fragement生命週期來實現這一目的。一般presenter是綁定在該生命週期上的。因此初始化或者清理一些東西等操做(例如撤銷異步運行任務)應該在 presenter.onAttach()和 presenter.onDetach() 上進行。咱們稍後會談到presenter如何使用setRetainInstanceState(true) 「避開」Fragment中的生命週期。我相信你們也注意到了, MvpPresenter是一個interface 。MVP模塊提供一個 MvpBasePresenter,這個MvpBasePresenter只持有View(是一個Fragment或Activity)的弱引用,從而避免內存泄露。所以,當presenter想要調用view方法時,咱們須要查看isViewAttached() 並使用getView()來獲取引用,以檢查view是否鏈接到了presenter。
一般Fragment會一直重複作某一件事。它在後臺加載數據,同時顯示加載view(即ProgressBar),並在屏幕上顯示加載的數據,或 者當加載失敗時顯示view錯誤。現在,下拉刷新支持很容易實現,由於SwipeRefreshLayout是Android支持庫的組成部分。爲了不 重複執行這一工做流,Mosby庫的MVP模塊提供了MvpLceView。
public interface MvpLceView<M> extends MvpView{ /** * 顯示一個加載中的視圖 * loading view 必須有個id 爲 R.id.loadingView的View * @param pullToRefresh 若是是true,那麼表示下拉刷新被觸發了 */ public void showLoading(boolean pullToRefresh); /** * 顯示 content view. * <content view 的id必須是R.id.contentView */ public void showContent(); /** * 顯示錯誤信息 */ public void showError(Throwable e,boolean pullToRefresh); /** * The data that should be displayed with {@link #showContent()} */ public void setData(M data); }
針對那種類型的view咱們能夠採用 MvpLceActivity implements MvpLceView 和 MvpLceFragment implements MvpLceView。二者均假設解析的xml佈局包括了含有R.id.loadingView,R.id.contentView和R.id.errorView的view。
示例
接下來要舉的例子Github上也有中,咱們使用CountriesAsyncLoader加載一系列的Country,並將其顯示在Fragment的RecyclerView中。你們能夠從這個連接https://db.tt/ycrCwt1L下載。
首先咱們要定義CountriesView這一view interface 。
public interface CountriesView extends MvpLceView<List<Country>>{ }
爲何要爲View定義接口呢?
1.由於定義了這個接口以後咱們能夠更改view的實現。咱們能夠簡單地把代碼從一個繼承自 Activity的實現轉移到繼承自 Fragment的實現。
2.模塊性:咱們能夠移動獨立的庫項目中的整個業務邏輯層、Presenter以及View 接口,而後把這個包含了Presenter的庫應用到各種app當中。下圖中左側是使用了嵌入在ViewPager中的Activity的kicker app,以及使用嵌入在ViewPager中的Fragment的meinVerein app,如圖1-5。 二者採用的是同一個定義了View接口和Presenter且測試了單元的庫。
因爲咱們能夠經過執行view接口來模擬view,因此咱們能夠很容易地編寫單元測試。還有一個更簡單的方法就是在presenter中引入java接口並使用模擬presenter對象來編寫單元測試。
還有一個良性反作用就是,定義了view接口以後,咱們不用直接從presenter再回調activity/fragment方法。咱們這樣區分開來是 由於在執行presenter時咱們在IDE自動完成上看到的方法只是關於view接口的方法。就我我的體會來講,我以爲這個方法很是有用,特別是團隊一 起工做的時候。須要注意的是,除了定義一個CountriesView接口以外,咱們還能夠採用MvpLceView<List> 。可是,定義一個專門的接口能夠提升代碼可讀性,而且未來能夠靈活地定義更多其餘的與View相關的方法。
Next we define our views xml layout file with the required ids:
下一步咱們須要按照指定的id來定義view xml 佈局文件.
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- Loading View --> <ProgressBar android:id="@+id/loadingView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:indeterminate="true" /> <!-- Content 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> <!-- Error view --> <TextView android:id="@+id/errorView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </FrameLayout>
CountriesPresenter控制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(); } }
實現CountriesView接口 的CountriesFragment 以下所示:
public class CountriesFragment extends MvpLceFragment<SwipeRefreshLayout,List<Country>,CountriesView,CountriesPresenter> implements CountriesView,SwipeRefreshLayout.OnRefreshListener{ @InjectView(R.id.recyclerView)RecyclerViewrecyclerView; CountriesAdapteradapter; @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); } }
代碼數量也並非不少嘛,對吧?這是由於基類已經執行了從加載view到content view或error view的轉換。咱們可能第一眼看到那一列MvpLceFragment類屬參數會以爲灰心。可是我要解釋一下:第一種類屬參數表明的是content view的類型;第二種是指以fragment顯示的Model;第三種是View接口;最後一種是Presenter的類型。總結起來就是:MvpLceFragment<AndroidView, Model, View接口, Presenter>。
你們可能還注意到的一個點就是 getLayoutRes(),它是MosbyFragment引入的用於解析xml view佈局的速記法。
@Override public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState){ Return inflater.inflate(getLayoutRes(),container,false); }
所以,咱們不用重寫onCreateView(),只需重寫getLayoutRes()。通常來講,onCreateView()只能建立view而onViewCreated()須要被重寫,以便爲RecyclerView初始化Adapter等項。所以,千萬不要忘記調用super.OnViewCreated();
看到這裏你們應該大概瞭解瞭如何運用Mosby庫。Mosby中的ViewState模塊能幫助咱們在Android開發中解決一些棘手的難題:處理屏幕旋轉。
問:若是把正在運行country這個例子的app並顯示了一列country的設備從橫屏旋轉到豎屏,會出現什麼狀況?
答:你們到這個視頻連接https://youtu.be/9iSBGEIZmUw中看看,結果是一個新的 CountriesFragment會被實例化,app開始顯示ProgressBar(並從新加載country列表)而再也不在RecyclerView中顯示country列表(屏幕旋轉前的狀態)
Mosby引入了ViewState來解決這個問題。原理就是,咱們跟蹤presenter從關聯的View中調用的方法。好比,presenter調用的是view.showContent(),一旦showContent()被調用,view就會意識到其狀態變動爲「showing content」,從而view把這一信息存儲到一個ViewState。若是view在方向改變過程當中遭到破壞,那麼ViewState 就會被存儲到Activity.onSaveInstanceState(Bundle) 或 Fragment.onSaveInstanceState(Bundle) 中,並在Activity.onCreate(Bundle) 或Fragment.onActivityCreated(Bundle)中修復。
因爲不是每種數據都能存儲在Bundle中,因此不一樣的數據類型採用不一樣的ViewState 實現:數據類型ArrayList採用ArrayListLceViewState;數據類型Parcelable 採用Parcelable DataLceViewState;數據類型Serializeable採用SerializeableLceViewState。若是使用的是一個可保持( Retaining )的Fragment,那麼 ViewState在屏幕旋轉時不會被破壞,因此也就不須要存儲到Bundle中。所以,它能夠存儲任何類型的數據。在這種狀況下,咱們須要使用RetainingFragmentLceViewState。存儲一個ViewState比較容易。因爲咱們的架構比較整潔,咱們的View又有接口,ViewState 能夠向presenter同樣經過調用一樣的接口方法來修復相關聯的view。舉個例子,MvpLceView通常有3種狀態,即:顯示showContent(),showLoading()和showError(),因此ViewState自己會調用相應的方法來修復view的狀態。
那只是一些內部構件。若是你們想編寫自定義的ViewState,瞭解以上內容就夠了。ViewStates的使用很是簡單。事實上,要把MvpLceFragment 遷移到MvpLceViewStateFragment ,咱們只須要另外執行createViewState() 和 getData()。下面咱們就在CountriesFragment中實踐一下吧:
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(Viewview,@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); } }
以上就是所有過程啦。咱們沒必要更改presenter或其餘代碼。這裏 是一個關於咱們的得到ViewState支持的CountriesFragment的視頻。在這個視頻中咱們能夠看到,view在方位轉變以後仍然處於同 樣的「狀態」,即,view橫屏顯示country列表,隨後橫屏顯示country列表。View能橫屏顯示下拉刷新指示,變動爲豎屏時也能顯示。
ViewState確實是一個強大且靈活的概念。看到這裏我相信你們都瞭解了LCE (Loading-Content-Error) ViewState的易用性。下面咱們就一塊兒來編寫本身的View和ViewState吧。咱們的View只顯示兩類不一樣的數據對象:A和B。結果應該像 這個視頻https://youtu.be/9iSBGEIZmUw 中演示的這樣:
你們內心確定以爲,這也不怎麼樣啊!別介啊,我只是想演示一下建立本身的ViewState是一件多麼容易的事。
View 接口和數據對象(model)以下所示:
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); }
在這個簡單的例子中咱們沒有加入業務邏輯層。由於咱們假設在實際的app中若是有業務邏輯層的話會使整個生成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); } } }
咱們定義了實現了MyCustomView接口的MyCustomActivity。
public class MyCustomActivity extends MvpViewStateActivity<MyCustomPresenter> implements MyCustomView{ @InjectView(R.id.textViewA) TextViewaView; @InjectView(R.id.textViewB) TextViewbView; @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 onNew ViewStateInstance(){ 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做爲基類。咱們採用的是最廣泛的支持 ViewState的MvpViewStateActivity做爲基類。基本上咱們的View只顯示aView 或 bView。
在onNew ViewStateInstance()中,咱們須要明確在第一個Activity運行時須要作 什麼,由於先前並不存在ViewState 例子用於修復。在showA(A a) 和 showB(B b)中,咱們須要將顯示A 或 B的信息存儲到ViewState。到這一步,咱們就差很少完成了,如今只差MyCustomViewState執行這一 步啦:
ublic 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保存到從Activity.onSaveInstanceState()調用的 saveInstanceState()中,而且在從Activity.onCreate()調用的restoreInstanceState()中修復viewstate的數據。apply()方法將會從Activity中調用以修復view state。咱們像presenter同樣經過調用一樣的View interface 方法showA() 或 showB()來實現這一操做。
你們能夠看到,咱們須要把ViewState保存到從Activity.onSaveInstanceState()調用的 saveInstanceState()中,而且在從Activity.onCreate()調用的restoreInstanceState()中修復viewstate的數據。apply()方法將會從Activity中調用以修復view state。咱們像presenter同樣經過調用一樣的View interface 方法showA() 或 showB()來實現這一操做。
這個外部的ViewState把view state修復的複雜性和職責從Activity代碼中剝離,併入到這個單獨的類中。而編寫ViewState類的單元測試要比Activity類的單元測試容易得多。
一般,Presenter會管理後臺線程。Presenter如何處理後臺線程取決於它所關聯的Activity或者Fragment ,具體分爲兩種狀況:
1.豎屏狀況下啓動應用
2.實例化Fragment時會調用onCreate()、onCreateView()、createPresenter(), 而後經過調用presenter的attachView()函數將View關聯到Presenter中。
3. 下一步咱們旋轉手機屏幕,從豎屏切換到橫屏;
4. 此時onDestroyView() 會調用,而onDestroyView() 又會調用presenter的detachView(true)函數。 咱們注意到detachView有個參數爲true,這是告訴presenter這個Fragment是可持有的Fragment(不然這個參數應該爲 false)。經過這個參數,presenter就知道它不須要取消正在運行的後臺任務;
5. 應用如今是橫屏狀態了,在旋轉時onCreateView方法會被調用,可是createPresenter()函數不會被調用,由於咱們會對 presenter 進行不爲空的判斷,當presenter爲空時才調用createPresenter()函數。而Fragment的 setRetainInstanceState(true)會保持這個presenter對象,所以presenter此時不會被從新建立;
6. 在調用了presenter的attachView()以後新建立的View會被從新關聯到presenter中。
7. ViewState會被恢復,可是沒有後臺任務會被取消,所以也沒有後臺任務須要從新啓動。
8.咱們採用非保持fragment在豎屏狀況下啓動app。
9.Fragment被實例化以後,調用onCreate(), onCreateView(),和createPresenter(),而後經過調用presenter.attachView()將view(fragment)附着到presenter。
10.下一步咱們旋轉設備屏幕,從豎屏切換到橫屏。
11.此時onDestroyView() 會調用,而onDestroyView() 又會調用presenter的detachView(true)函數。Presenter取消後臺任務。
12. onSaveInstanceState(Bundle)被調用, ViewState被保存到Bundle中。
13. App如今出於橫屏狀態。新的Fragment被實例化並調用onCreate(),onCreateView()和 createPresenter()來建立一個新的presenter例子,經過調用presenter.attachView()將新的view附着到新的presenter
14. ViewState會從Bundle中恢復,且view的狀態也會被恢復。若是ViewState是showLoading,那麼presenter會從新啓動後臺線程來加載數據。
15. 如下是得到ViewState支持的Activity的生命週期圖解,如圖1-6:
如下是得到ViewState支持的Fragment的生命週期圖解, 如圖 1-7:
Mosby提供了 LceRetrofitPresenter 和 LceCallback。爲得到LCE方法showLoading(), showContent() 和 showError()支持的Retrofit編寫presenter ,幾行代碼就能搞定。
public class MembersPresenter extends LceRetrofitPresenter<MembersView,List<User>>{ private GithubApigithubApi; public MembersPresenter(GithubApi githubApi){ this.githubApi=githubApi; } public void loadSquareMembers(boolean pullToRefresh){ githubApi.getMembers("square",new LceCallback(pullToRefresh)); } }
想在不依靠注入式的狀況下寫應用?Ted Mosby告訴你,這是行不通滴!Dagger是java依賴注入式框架最經常使用的方法,也是Android開發者們的心頭好。Mosby支持 Dagger1。Mosby經過一個叫作getObjectGraph()的方法提供Injector界面。一般,咱們的應用模塊很是普遍。要想輕鬆分享 這一模塊,咱們須要把android.app.Application納入子類,使其執行Injector。以後全部的Activity和 Fragment均可以經過調用getObjectGraph()來存取ObjectGraph,由於DaggerActivity and DaggerFragment也 都是Injector。咱們也能夠經過重寫Activity 或 Fragment中的 getObjcetGraph() ,從而調用plus(Module)以增長模塊。我我的已經用到Dagger2了,它與 Mosby也兼容。你們能夠在Github上找到關於Dagger1 和 Dagger2的示例。點此這個連接https://db.tt/3fVqVdAzDagger1示例 apk;點此這個連接https://db.tt/z85y4fSYDagger2 示例 apk。
Observables贊爆了!如今稍微潮一點的小夥兒們都用RxJava了好嗎!你猜結果怎麼着?RxJava確實是太酷了!因此,Mosby給你們提供一個本質上是Subscriber的MvpLceRxPresenter,它能幫咱們自動處理onNext(), onCompleted() 和 onError()並回調相應的LCE方法,好比showLoading(), shwoContent() 和 showError()。 它還將 RxAndroid 附帶到observerOn() Android主要 UI 線程。你可能以爲,要是用了RxJava的話就再也不須要Model View Presenter了。呃,那只是你的一家之言。在我看來,把View和Model清晰地區分開來很是重要。並且我也認爲其中的某些好用的功能在沒有 MVP的狀況下不容易執行。最後,你們要是還想回到過去那個Activity和Fragment包含了上千條又臭又長的代碼行時代,那麼我祝你在麪條式代 碼的地獄裏過得愉快。好了,廢話很少說,我介紹的方法不屬於麪條式代碼是由於Observerables引入了一個結構齊整的工做流,把Activity 或Fragment作成一個BLOB的想法已經近在咫尺了。
你們可能注意到這裏存在着一個測試模塊。這個模塊用於Mosby庫的內部測試。可是,它也能夠爲咱們本身的app所用。它使用 Robolectric爲咱們的LCE Presenter, Activities 和 Fragments提供單元測試模板。它的基本功能是查看測試中的Presenter是否正確工做:經過觀察presenter時候調用 showLoading(),
showContent() 和 showError()。咱們還能夠驗證setData()中的數據。因此咱們能夠爲Presenter和底層編寫相似黑匣子的測試。Mosby的測試模塊也提供了測試MvpLceFragment 或 MvpLceActivity的 可能性。它至關於一種「精簡版」的UI 測試。這些測試經過查看xml佈局是否包含R.id.loadingView, R.id.contentView 和R.id.errorView之類 的指定id、loadingView是否可視,在加載view時,是不是錯誤的view可視、content view可否處理由setData()提交的已加載數據等方面來檢驗Fragment或Activity是否正常工做,是否遇到crashing。它和 Espresso類的UI測試並不相同。我以爲沒有必要爲LCE View單獨寫一個UI 測試。
如下是Ted Mosby庫的一些測試小建議:
1. 編寫傳統的單元測試來測試業務邏輯層和model。
2. 使用MvpLcePresenterTest來測試presenter。
3.使用MvpLceFragmentTest 和 MvpLceActivityTest來測試MvpLceFragment 和 Activity。
4.若是有必要,可使用Espresso來編寫UI測試。
測試模塊還沒有完成。你們能夠看到這個模塊是測試版,由於Robolectric 3.0還沒完成,並且Android gradle plugin也沒用徹底支持傳統的單元測試。android gradle plugin 1.2應該會好得多。Robolectric 和 androids gradle plugin能夠用了。
以後我會再寫一篇關於Mosby,Dagger,Retrofit和RxJava單元測試的博客。