轉(zhuǎn):安卓中的Model-View-Presenter模式介紹

英文原文:Introduction to Model-View-Presenter on Android

翻譯原文:http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0425/2782.html

這是一篇觀點(diǎn)比較激進(jìn)的文章,完全否定了MVC模式在安卓開(kāi)發(fā)的中的意義,認(rèn)為其是沒(méi)有任何用處的。這篇文章因?yàn)槭褂昧遂o態(tài)變量來(lái)定義Presenter,因此在原文的評(píng)論部分也受到一些爭(zhēng)議。不過(guò)我覺(jué)得還是從中學(xué)到了一些思想。我甚至同意MVC模式在安卓開(kāi)發(fā)的中毫無(wú)意義的說(shuō)法。沒(méi)有完美的文章。另外關(guān)于MVP,還看到了一篇思路更清晰的文章,也準(zhǔn)備翻譯出來(lái)。

這是一篇安卓中MVP模式的詳細(xì)教程,從最簡(jiǎn)單的例子到最佳實(shí)踐。本文還介紹了一個(gè)讓在安卓中使用MVP模式變得非常簡(jiǎn)單的library。

它是不是很簡(jiǎn)單,我們?nèi)绾尾拍軓闹蝎@益?

什么是MVP

.View是指顯示數(shù)據(jù)并且和用戶(hù)交互的層。在安卓中,它們可以是一個(gè)Activity,一個(gè)Fragment,一個(gè)android.view.View或者是一個(gè)Dialog。

.Model是數(shù)據(jù)源層。比如數(shù)據(jù)庫(kù)接口或者遠(yuǎn)程服務(wù)器的api。

.Presenter是從Model中獲取數(shù)據(jù)并提供給View的層,Presenter還負(fù)責(zé)處理后臺(tái)任務(wù)。

MVP是一個(gè)將后臺(tái)任務(wù)和activities/views/fragment分離的方法,讓它們獨(dú)立于絕大多數(shù)跟生命周期相關(guān)的事件。這樣應(yīng)用就會(huì)變得更簡(jiǎn)單,整個(gè)應(yīng)用的穩(wěn)定性提高10倍以上,代碼也變得更短,可維護(hù)性增強(qiáng),程序員也不會(huì)過(guò)勞死了~~。

為什么要在安卓上使用MVP

原因之一: 盡量簡(jiǎn)單

如果你還沒(méi)有閱讀過(guò)這篇文章,閱讀它:Kiss原則。- kiss是Keep It Stupid Simple或者Keep It Simple, Stupid的縮寫(xiě)。

.絕大多數(shù)的安卓程序都只使用了View-Model架構(gòu)。

.程序員被絞盡了復(fù)雜的界面開(kāi)發(fā)中,而不是解決事務(wù)邏輯。

在應(yīng)用中使用Model-View的壞處是“每個(gè)東西之間都是相互關(guān)聯(lián)的”如下圖:

如果上面的圖解看起來(lái)還不夠復(fù)雜,那么想想這些情況:每個(gè)view可能在任意的時(shí)間出現(xiàn)或者消失,view數(shù)據(jù)需要保存與恢復(fù),在臨時(shí)的view上掛載一個(gè)后臺(tái)任務(wù)。

而與“每個(gè)東西之間都是相互關(guān)聯(lián)的”的相反選擇是使用一個(gè)萬(wàn)能對(duì)象(god object)。注:god object是指一個(gè)對(duì)象/例程在系統(tǒng)中做了太多的事情,或者說(shuō)是有太多不怎么相關(guān)的事情放在一個(gè)對(duì)象/例程里面來(lái)完成。

god object過(guò)于復(fù)雜,他的不同部分無(wú)法重用、測(cè)試,無(wú)法輕易的debug和重構(gòu)。

使用MVP

.復(fù)雜的任務(wù)被分割成簡(jiǎn)單的任務(wù)。

.更小的對(duì)象,更少的bug。

.更好測(cè)試

MVP的view層變得如此簡(jiǎn)單,在請(qǐng)求數(shù)據(jù)的時(shí)候甚至不需要使用回調(diào)。view的邏輯變得非常直接。

原因之二: 后臺(tái)任務(wù)

當(dāng)你需要寫(xiě)一個(gè)Activity,F(xiàn)ragment或者一個(gè)自定義View的時(shí)候,你可以將所有和后臺(tái)任務(wù)相關(guān)的方法放在一個(gè)外部的或者靜態(tài)的類(lèi)中。這樣你的后臺(tái)任務(wù)就不會(huì)再與Activity相關(guān)聯(lián),不會(huì)在泄漏內(nèi)存同時(shí)也不會(huì)依賴(lài)于Activity的重建。我們稱(chēng)這樣的一個(gè)類(lèi)為“Presenter”。注:要理解此話的含義最好先看懂第一個(gè)MVP示例的代碼。

雖然有一些方法可以解決后臺(tái)任務(wù)的問(wèn)題,但是沒(méi)有一種和MVP一樣可靠。

為什么這是可行的

下面的圖解顯示了在configuration改變或者發(fā)生out-of-memory事件的情況下應(yīng)用的不同部分所發(fā)生的事情。每一個(gè)開(kāi)發(fā)者都應(yīng)該知道這些數(shù)據(jù),但是這些數(shù)據(jù)并不好發(fā)現(xiàn)。

|Case1|Case2|Case3

|A?configuration|Anactivity|A?process

|change|restart|restart

----------------------------------------|-------------|------------|------------

Dialog|reset|reset|reset

Activity,View,Fragment|save/restore|save/restore|save/restore

FragmentwithsetRetainInstance(true)|nochange|save/restore|save/restore

Staticvariablesandthreads|nochange|nochange|reset

情景1:configuration的改變通常發(fā)生在旋轉(zhuǎn)屏幕,修改語(yǔ)言設(shè)置,鏈接外部的模擬器等情況下。要知道更多的configuration change事件請(qǐng)閱讀:configChanges

情景2:Activity的重啟發(fā)生在當(dāng)用戶(hù)在開(kāi)發(fā)者選項(xiàng)中選中了“Don't keep activities”(“中文下為 不保留活動(dòng)”)的復(fù)選框,然后另一個(gè)Activity在最頂上的時(shí)候。

情景3:進(jìn)程的重啟發(fā)生在應(yīng)用運(yùn)行在后臺(tái),但是這個(gè)時(shí)候內(nèi)存不夠的情況下。

結(jié)論

現(xiàn)在你可以發(fā)現(xiàn),一個(gè)擁有setRetainInstance(true)的Fragment并沒(méi)有帶來(lái)幫助 - 我們還是要保存和/恢復(fù)這種fragment的狀態(tài)。因此我們可以去掉可保持Fragment的情景,把問(wèn)題簡(jiǎn)單化。Occam's razor.

|A?configuration|

|change,|

|Anactivity|A?process

|restart|restart

----------------------------------------|-------------|-------------

Activity,View,Fragment,DialogFragment|save/restore|save/restore

Staticvariablesandthreads|nochange|reset

現(xiàn)在看起來(lái)就好多了。我們只需要寫(xiě)兩部分代碼來(lái)實(shí)現(xiàn)任意情況下完全恢復(fù)應(yīng)用的狀態(tài):

.保存/恢復(fù)Activity, View, Fragment, DialogFragment;

.在進(jìn)程重啟的情況下重新開(kāi)啟后臺(tái)請(qǐng)求。

第一部分我們可以通過(guò)常規(guī)的Android API方式來(lái)實(shí)現(xiàn),第二部分就是Presenter的工作了。Presenter可以記住哪個(gè)請(qǐng)求應(yīng)該被執(zhí)行,并且在執(zhí)行期間如果進(jìn)程重啟,Presenter可以重新執(zhí)行這些請(qǐng)求。

一個(gè)簡(jiǎn)單的例子 (未使用MVP)

這個(gè)例子將從遠(yuǎn)程服務(wù)器中加載與顯示一些item元素(就是顯示在ListView中的意思)。如果遇到錯(cuò)誤會(huì)顯示一個(gè)toast提示。

我推薦使用RxJava來(lái)建立presenter,因?yàn)檫@個(gè)庫(kù)可以讓數(shù)據(jù)流的控制更簡(jiǎn)單。

我還要感謝那個(gè)創(chuàng)立了一個(gè)簡(jiǎn)單api的小伙伴,我的例子中用到了它:The Internet Chuck Norris Database。作者的遠(yuǎn)程數(shù)據(jù)就是來(lái)自于這個(gè)api。貌似是一個(gè)提供笑話內(nèi)容的api。

不使用 MVP示例 00:

publicclassMainActivityextendsActivity{

publicstaticfinalStringDEFAULT_NAME="Chuck?Norris";

privateArrayAdapteradapter;

privateSubscriptionsubscription;

@Override

publicvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

ListViewlistView=(ListView)findViewById(R.id.listView);

listView.setAdapter(adapter=newArrayAdapter<>(this,R.layout.item));

requestItems(DEFAULT_NAME);

}

@Override

protectedvoidonDestroy(){

super.onDestroy();

unsubscribe();

}

publicvoidrequestItems(Stringname){

unsubscribe();

subscription=App.getServerAPI()

.getItems(name.split("\\s+")[0],name.split("\\s+")[1])

.delay(1,TimeUnit.SECONDS)

.observeOn(AndroidSchedulers.mainThread())

.subscribe(newAction1(){

@Override

publicvoidcall(ServerAPI.Responseresponse){

onItemsNext(response.items);

}

},newAction1(){

@Override

publicvoidcall(Throwableerror){

onItemsError(error);

}

});

}

publicvoidonItemsNext(ServerAPI.Item[]items){

adapter.clear();

adapter.addAll(items);

}

publicvoidonItemsError(Throwablethrowable){

Toast.makeText(this,throwable.getMessage(),Toast.LENGTH_LONG).show();

}

privatevoidunsubscribe(){

if(subscription!=null){

subscription.unsubscribe();

subscription=null;

}

}

}

注:別被RxJava嚇到,你就當(dāng)成一般的異步請(qǐng)求就行了。

一個(gè)有經(jīng)驗(yàn)的開(kāi)發(fā)者應(yīng)該注意到這個(gè)簡(jiǎn)單的例子存在很?chē)?yán)重的問(wèn)題:

.每次用戶(hù)翻轉(zhuǎn)屏幕的時(shí)候都會(huì)開(kāi)始請(qǐng)求 - app做了多余實(shí)際需要的請(qǐng)求,并且用戶(hù)在旋轉(zhuǎn)屏幕之后會(huì)觀察到一段時(shí)間的空白屏幕。

.如果用戶(hù)翻轉(zhuǎn)屏幕的此時(shí)很頻繁會(huì)導(dǎo)致內(nèi)存泄漏 - 每次回調(diào)都會(huì)保存一個(gè)對(duì)MainActivity的引用,在請(qǐng)求運(yùn)行的時(shí)候這個(gè)引用將保存在內(nèi)存中。這幾乎會(huì)必然導(dǎo)致應(yīng)用因?yàn)閛ut-of-memory錯(cuò)誤或者運(yùn)行緩慢而崩潰。

譯者注:為什么平時(shí)我們沒(méi)有發(fā)現(xiàn)這樣的問(wèn)題?因?yàn)槲覀兺耆蝗タ紤]用戶(hù)頻繁旋轉(zhuǎn)屏幕的情況,我們認(rèn)為用戶(hù)這樣用手機(jī)是找虐,還有,絕大多數(shù)的中文應(yīng)用都禁止屏幕旋轉(zhuǎn),只有豎屏,因此就避免了這種問(wèn)題的發(fā)生。

使用MVP示例 01:

publicclassMainPresenter{

publicstaticfinalStringDEFAULT_NAME="Chuck?Norris";

privateServerAPI.Item[]items;

privateThrowableerror;

privateMainActivityview;

publicMainPresenter(){

App.getServerAPI()

.getItems(DEFAULT_NAME.split("\\s+")[0],DEFAULT_NAME.split("\\s+")[1])

.delay(1,TimeUnit.SECONDS)

.observeOn(AndroidSchedulers.mainThread())

.subscribe(newAction1(){

@Override

publicvoidcall(ServerAPI.Responseresponse){

items=response.items;

publish();

}

},newAction1(){

@Override

publicvoidcall(Throwablethrowable){

error=throwable;

publish();

}

});

}

publicvoidonTakeView(MainActivityview){

this.view=view;

publish();

}

privatevoidpublish(){

if(view!=null){

if(items!=null)

view.onItemsNext(items);

elseif(error!=null)

view.onItemsError(error);

}

}

}

嚴(yán)格意義上來(lái)說(shuō)MainPresenter有三個(gè)事件:onNext, onError, onTakeView(onNext指代view.onItemsNext,同理onError指代view.onItemsError)。這三個(gè)事件在publish()方法中結(jié)合到了一起。onNext和onError的值被發(fā)布給了onTakeView()方法提供的MainActivity的實(shí)例。

publicclassMainActivityextendsActivity{

privateArrayAdapteradapter;

privatestaticMainPresenterpresenter;

@Override

publicvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

ListViewlistView=(ListView)findViewById(R.id.listView);

listView.setAdapter(adapter=newArrayAdapter<>(this,R.layout.item));

if(presenter==null)

presenter=newMainPresenter();

presenter.onTakeView(this);

}

@Override

protectedvoidonDestroy(){

super.onDestroy();

presenter.onTakeView(null);

if(isFinishing())

presenter=null;

}

publicvoidonItemsNext(ServerAPI.Item[]items){

adapter.clear();

adapter.addAll(items);

}

publicvoidonItemsError(Throwablethrowable){

Toast.makeText(this,throwable.getMessage(),Toast.LENGTH_LONG).show();

}

}

MainActivity創(chuàng)建MainPresenter,并讓它在onCreate/onDestroy的周期之外。MainActivity用靜態(tài)變量來(lái)引用MainPresenter,因此每次進(jìn)程因?yàn)閛ut-of-memory事件重啟的時(shí)候,MainActivity都會(huì)檢查presenter是否還在,如果必要再新建一個(gè)。是的,使用靜態(tài)變量看起來(lái)會(huì)覺(jué)得讓人不舒服,但是稍后我們會(huì)告訴你如何好看些:

主要的考慮是:

示例程序不會(huì)在每次切換屏幕的時(shí)候都開(kāi)始一個(gè)新的請(qǐng)求。

如果進(jìn)程重啟,示例程序會(huì)重新加載數(shù)據(jù)。

在MainActivity銷(xiāo)毀(destroyed)的時(shí)候MainPresenter不會(huì)再持有對(duì)MainActivity的引用,因此不會(huì)在切換屏幕的時(shí)候發(fā)生內(nèi)存泄漏,而且沒(méi)必要去unsubscribe請(qǐng)求。

Nucleus

Nucleus是我從Mortar庫(kù)和Keep It Stupid Simple這篇文章得到的靈感而建立的庫(kù)。

下面列出其特點(diǎn):

1.支持在View、Fragment或者Activity的Bundle中保存與恢復(fù)Presenter的狀態(tài)。Presenter可以將請(qǐng)求參數(shù)保存在這個(gè)bundle中,在稍后重啟請(qǐng)求。

2.只需一行代碼就能將請(qǐng)求的結(jié)果與錯(cuò)誤信息交給view,你不需要寫(xiě)什么!= null之類(lèi)的檢查代碼。

3.presenter允許擁有多個(gè)View的實(shí)例。不過(guò)你不能在用Dagger實(shí)例化的presenter中這樣使用。

4.支持只用一行代碼將presenter和view綁定。

5.提供一些現(xiàn)成的基類(lèi):NucleusView, NucleusFragment, NucleusSupportFragment, NucleusActivity。你可以將他們的代碼拷貝出來(lái)改造出一個(gè)自己的類(lèi)以利用Nucleus的presenter。

6.支持在進(jìn)程重啟的時(shí)候自動(dòng)重啟一個(gè)請(qǐng)求,以及在銷(xiāo)毀(onDestroy)期間自動(dòng)取消RxJava的訂閱。

7.最后,它非常簡(jiǎn)單,任何一個(gè)開(kāi)發(fā)者都能理解。只有Presenter的驅(qū)動(dòng)只有180行代碼,而對(duì)于RxJava的支持只有230行代碼。

Nucleus的例子example 02

publicclassMainPresenterextendsRxPresenter{

publicstaticfinalStringDEFAULT_NAME="Chuck?Norris";

@Override

protectedvoidonCreate(BundlesavedState){

super.onCreate(savedState);

App.getServerAPI()

.getItems(DEFAULT_NAME.split("\\s+")[0],DEFAULT_NAME.split("\\s+")[1])

.delay(1,TimeUnit.SECONDS)

.observeOn(AndroidSchedulers.mainThread())

.compose(this.deliverLatestCache())

.subscribe(newAction1(){

@Override

publicvoidcall(ServerAPI.Responseresponse){

getView().onItemsNext(response.items);

}

},newAction1(){

@Override

publicvoidcall(Throwablethrowable){

getView().onItemsError(throwable);

}

});

}

}

@RequiresPresenter(MainPresenter.class)

publicclassMainActivityextendsNucleusActivity{

privateArrayAdapteradapter;

@Override

publicvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

ListViewlistView=(ListView)findViewById(R.id.listView);

listView.setAdapter(adapter=newArrayAdapter<>(this,R.layout.item));

}

publicvoidonItemsNext(ServerAPI.Item[]items){

adapter.clear();

adapter.addAll(items);

}

publicvoidonItemsError(Throwablethrowable){

Toast.makeText(this,throwable.getMessage(),Toast.LENGTH_LONG).show();

}

}

就如你看到的那樣,這個(gè)例子比前面的例子要簡(jiǎn)短多了。Nucleus可以創(chuàng)建/銷(xiāo)毀/保存 presenter,附加或者解除和一個(gè)view的關(guān)系,并且自動(dòng)向附加的view發(fā)送請(qǐng)求。

MainPresenter的代碼變短是因?yàn)槲覀兪褂昧薲eliverLatestCache()操作將數(shù)據(jù)源發(fā)出的所有數(shù)據(jù)與錯(cuò)誤信息延遲到了view可用之后。它還能將數(shù)據(jù)緩存到內(nèi)存中,因此可以在onfiguration change的時(shí)候重用。

警告!這里有一個(gè)注解!在安卓的世界里,如果你使用了注解,最好檢查一下它是否會(huì)影響性能。

MainActivity的代碼變簡(jiǎn)單了是因?yàn)閜resenter的創(chuàng)建是由NucleusActivity管理的。你只需要寫(xiě)上@RequiresPresenter(MainPresenter.class) 就能綁定presenter。我在Galaxy S(2010年的設(shè)備)上的檢測(cè)結(jié)果顯示,注解在這里

只花費(fèi)了不到0.3ms。只在實(shí)例化view的時(shí)候才會(huì)發(fā)生,因此注解在這里對(duì)性能的影響可以忽略。

更多示例

帶有保持請(qǐng)求參數(shù)的拓展示例在這里:Nucleus Example.

帶有單元測(cè)試的例子:Nucleus Example With Tests

deliverLatestCache() 方法

這個(gè)RxPresenter的工具方法有三個(gè)變種:

deliver() will just delay all onNext, onError and onComplete emissions until a View becomes available. Use it for cases when you're doing a one-time request, like logging in to a web service.Javadoc

deliverLatest() will drop the older onNext value if a new onNext value is available. If you have an updatable source of data this will allow you to not accumulate data that is not necessary.Javadoc

deliverLatestCache() is the same as deliverLatest() but in addition it will keep the latest result in memory and will re-deliver it when another instance of a view becomes available (i.e. on configuration change). If you don't want to organize save/restore of a request result in your view (in case if a result is big or it can not be easily saved into Bundle) this method will allow you to make user experience better.Javadoc

Presenter的生命周期

Presenter的生命周期要比安卓組建的生命周期簡(jiǎn)短得多

void onCreate(Bundle savedState) - 在Presenter創(chuàng)建的時(shí)候調(diào)用Javadoc

void onDestroy() - 在用戶(hù)離開(kāi)一個(gè)view的時(shí)候調(diào)用Javadoc

void onSave(Bundle state) - 在View的onSaveInstanceState同時(shí)也是Presenter的狀態(tài)保持的時(shí)候被調(diào)用Javadoc

void onTakeView(ViewType view) -? 在Activity或者Fragment的Resume()或者android.view.View#onAttachedToWindow()的時(shí)候調(diào)用.Javadoc

void onDropView() - 在Activity或者Fragment的onPause()或者android.view.View#onDetachedFromWindow()的時(shí)候調(diào)用.Javadoc

View的生命周期與view棧

通常來(lái)說(shuō)你的view(比如fragment或者自定義的view)在用戶(hù)的交互過(guò)程中掛載與解掛(attached and detached)都是隨機(jī)發(fā)生的。 這倒是不讓presenter在view每次解掛(detached)的時(shí)候都銷(xiāo)毀的一個(gè)啟發(fā)。你可以在任何時(shí)候掛載與解掛view,但是presenter可以在這些行為中幸存下來(lái),繼續(xù)后臺(tái)的工作。

關(guān)于view的周期有一個(gè)問(wèn)題:fragment會(huì)因?yàn)閏onfiguration change或者從棧中去掉而不知道自己是否被解掛(detached)。

Nucleus view默認(rèn):只有在activity結(jié)束的時(shí)候,在view的onDetachedFromWindow()/onDestroy()期間才會(huì)銷(xiāo)毀presenter。

因此,如果你要在Activity正常的生命期間銷(xiāo)毀一個(gè)view,你必須向view發(fā)出presenter也必須銷(xiāo)毀的信號(hào)。通過(guò)公共方法NucleusLayout.destroyPresenter()和NucleusFragment.destroyPresenter()來(lái)做這個(gè)事情。

比如,下面是fragment manager的pop()操作在我的一個(gè)項(xiàng)目里是如何工作的:

fragment=fragmentManager.findFragmentById(R.id.fragmentStackContainer);

fragmentManager.popBackStackImmediate();

if(fragmentinstanceofNucleusFragment)

((NucleusFragment)fragment).destroyPresenter();

同樣在fragment的replace操作中也要做相同的事情,在最底部的fragment銷(xiāo)毀的時(shí)候也要如此。

你可能會(huì)決定在每次view從Activity解掛的時(shí)候都銷(xiāo)毀presenter來(lái)避免這樣的問(wèn)題,但是如果這樣的話,在view銷(xiāo)毀的時(shí)候你無(wú)法繼續(xù)后臺(tái)任務(wù)。所以這一節(jié)的?"view recycling"完全留你你自己考慮,也許有一天我會(huì)找到更好的解決辦法,如果你有一個(gè)辦法,請(qǐng)告訴我。

最佳實(shí)踐

在Presenter中保存你的請(qǐng)求參數(shù)

規(guī)則很簡(jiǎn)單:presenter的主要職能是管理請(qǐng)求。因此view不應(yīng)該去處理或者開(kāi)始請(qǐng)求。從view的角度來(lái)看,后臺(tái)任務(wù)是永不消失的,總是會(huì)返回一個(gè)結(jié)果或者錯(cuò)誤信息的,不需要任何回調(diào)的。

publicclassMainPresenterextendsRxPresenter{

privateStringname=DEFAULT_NAME;

@Override

protectedvoidonCreate(BundlesavedState){

super.onCreate(savedState);

if(savedState!=null)

name=savedState.getString(NAME_KEY);

...

@Override

protectedvoidonSave(@NonNullBundlestate){

super.onSave(state);

state.putString(NAME_KEY,name);

}

我推薦你使用酷爆了的Icepick庫(kù)。在不使用運(yùn)行時(shí)注解的前提下,它減少了代碼量并且簡(jiǎn)化了app的邏輯,所有的事情都在編譯過(guò)程中就完成了,是ButterKnife的好伴侶。

publicclassMainPresenterextendsRxPresenter{

@IcicleStringname=DEFAULT_NAME;

@Override

protectedvoidonCreate(BundlesavedState){

super.onCreate(savedState);

Icepick.restoreInstanceState(this,savedState);

...

@Override

protectedvoidonSave(@NonNullBundlestate){

super.onSave(state);

Icepick.saveInstanceState(this,state);

}

如果你有多個(gè)請(qǐng)求參數(shù),這個(gè)庫(kù)可以幫助你節(jié)省不少時(shí)間。你可以創(chuàng)建一個(gè)BasePresenter,然后將Icepick放到里面,所有的子類(lèi)將自動(dòng)保存被@Icicle注釋的變量,你再也不需要實(shí)現(xiàn)onSave了。這對(duì)于Activity和Fragment或者View也同樣適用。

Execute instant queries on the main thread in onTakeViewJavadoc

有時(shí)候我們的數(shù)據(jù)查詢(xún)量并不大,比如從數(shù)據(jù)庫(kù)中讀取少量的數(shù)據(jù)。雖然使用Nucleus創(chuàng)建一個(gè)可重啟的請(qǐng)求非常簡(jiǎn)單,但是你不需要每次都用。如果你在fragment創(chuàng)建的時(shí)候初始化一個(gè)后臺(tái)請(qǐng)求,即使只有幾毫秒,用戶(hù)也會(huì)看到一會(huì)兒的空白屏。因此為了代碼的簡(jiǎn)潔,也為了用戶(hù)的感受,使用主線程來(lái)初始化。

不要讓Presenter控制View

這種情況不好工作 - application的邏輯因?yàn)槭褂昧瞬蛔匀坏姆绞阶兊梅浅?fù)雜。

最自然的方式是用戶(hù)的操作流從view,到presenter到model最后到數(shù)據(jù)。這樣用戶(hù)才是控制應(yīng)用的源頭。對(duì)應(yīng)用的控制應(yīng)該來(lái)源于用戶(hù),而不是應(yīng)用的內(nèi)部結(jié)構(gòu)。從view,到presenter到model是很直接的形式,這樣的代碼也很好寫(xiě),操作流是這樣的user -> view -> presenter -> model -> data;但是像這樣的操作流:user -> view -> presenter -> view -> presenter -> model -> data,是違背了KISS原則的。

什么?Fragment?不好意思它是違背了這種自然操作流程的。他們太復(fù)雜。這里有一篇關(guān)于看待fragment的好文章:不提倡 Android Fragment。但是fragment的替代者Flow并沒(méi)有簡(jiǎn)化多少東西。

MVC

如果你熟悉MVC(Model-View-Controller)- 別那樣做。Model-View-Controller和MVP完全不同,也并沒(méi)有解決用戶(hù)界面開(kāi)發(fā)上的任何問(wèn)題。

什么是MVC?

Modelstands here for internal application state. It can or can not be connected with a storage.

Viewis the only thing that is partially common with MVP - it is a part of an application that rendersModelto the screen.

Controllerrepresents an input device, such as keyboard, mouse or joystick.

MVC在過(guò)去以鍵盤(pán)為驅(qū)動(dòng)的應(yīng)用中(比如游戲),是比較好的模式。沒(méi)有窗口和圖形用戶(hù)界面的交互-程序接受輸入(Controller),維護(hù)狀態(tài)(Model),以及顯示輸出(View)。數(shù)據(jù)與操作類(lèi)似于:controller -> model -> view.但是這種模式在安卓中完全無(wú)用。MVC有太多的困擾。人們認(rèn)為他們?cè)谑褂肕VC,其實(shí)使用的的MVP(web開(kāi)發(fā)者)。許多安卓開(kāi)發(fā)者認(rèn)為Controller應(yīng)該是控制view的東西,因此他們將view的邏輯從view中分離,創(chuàng)建一個(gè)輕量級(jí)的被代理Controller控制的view。我個(gè)人是沒(méi)有看出這種架構(gòu)的好處。

在數(shù)據(jù)復(fù)雜的項(xiàng)目中使用固定的數(shù)據(jù)結(jié)構(gòu)

AutoValue是做這件事的一個(gè)優(yōu)秀的庫(kù),在他的描述中有其優(yōu)點(diǎn)的列表,建議閱讀。有安卓的接口AutoParcel。使用固定數(shù)據(jù)對(duì)象的主要原因是你可以四處傳遞,而不用關(guān)心是否在程序的某個(gè)地方被修改了。而且它們是線程安全的。

結(jié)論

試試mvp吧,并告訴你的朋友。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容