文章轉(zhuǎn)自:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0331/2672.html
英文原文 Replace AsyncTask and AsyncTaskLoader with rx.Observable – RxJava Android Patterns
譯文原文 http://blog.csdn.net/zhaokaiqiang1992/article/details/44751003
在網(wǎng)上有很多關(guān)于RxJava入門指南的帖子,其中一些是基于Android環(huán)境的。但是,我想到目前為止,很多人只是喜歡他們所看到的這些,當(dāng)要 解決在他們的Android項(xiàng)目中出現(xiàn)的具體問題時(shí),他們并不知道如何或者是為什么要使用RxJava。在這一系列的文章中,我想要探索在我工作過的一些 依賴于RxJava架構(gòu)的Android項(xiàng)目中的模式。
在文章的開始,我想要處理一些Android開發(fā)者在使用RxJava的時(shí)候,很容易遇到的狀況。從這個(gè)角度,我將提供更高級(jí)和更合適的解決方案。在這一系列的文章中,我希望可以聽到其他開發(fā)者在使用RxJava的過程中解決類似的問題,或許他們和我發(fā)現(xiàn)的一樣呢。
問題一:后臺(tái)任務(wù)
Android開發(fā)者首先遇到的挑戰(zhàn)就是如何有效的在后臺(tái)線程中工作,然后在UI線程中更新UI。這經(jīng)常是因?yàn)樾枰獜膚eb service中獲取數(shù)據(jù)。對(duì)于已經(jīng)有相關(guān)經(jīng)驗(yàn)的你可能會(huì)說:“這有什么挑戰(zhàn)性?你只需要啟動(dòng)一個(gè)AsyncTask,然后所有的工作它就都給你做了?!?如果你是這樣想的,那么你有一個(gè)機(jī)會(huì)去改善這種狀況。這是因?yàn)槟阋呀?jīng)習(xí)慣了這種復(fù)雜的方式并且忘記這本應(yīng)該是很簡(jiǎn)潔的,或者是說你沒有處理所有應(yīng)該處理的 邊界情況。讓我們來談?wù)勥@個(gè)。
默認(rèn)的解決方案:AsyncTask
AsyncTask是在Android里面默認(rèn)的處理工具,開發(fā)者可以做里面一些長(zhǎng)時(shí)間的處理工作,而不會(huì)阻塞用戶界面。(注意:最近,AsyncTaskLoader用來處理一些更加具體的數(shù)據(jù)加載任務(wù),我們以后會(huì)再談?wù)勥@個(gè))
表面上,這似乎很簡(jiǎn)單,你定義一些代碼在后臺(tái)線程中運(yùn)行,然后定義一些代碼運(yùn)行在UI線程中,在后臺(tái)任務(wù)處理完之后,它在UI線程會(huì)處理從后臺(tái)任務(wù)傳遞過來的執(zhí)行結(jié)果。
private class CallWebServiceTask extends AsyncTask<String, Result, Void> {
protected Result doInBackground(String... someData) {
Result result = webService.doSomething(someData);
return result;
}
protected void onPostExecute(Result result) {
if (result.isSuccess() {
resultText.setText("It worked!");
}
}
}
使用AsyncTask的最大的問題是在細(xì)節(jié)的處理上,讓我們談?wù)勥@個(gè)問題。
錯(cuò)誤處理
這種簡(jiǎn)單用法的第一個(gè)問題就是:“如果出現(xiàn)了錯(cuò)誤怎么辦?”不幸的是,暫時(shí)沒有非常好的解決方案。所以很多的開發(fā)者最終要繼承AsyncTask, 然后在doInBackground()中包裹一個(gè)try/catch,返回一個(gè),然后根據(jù)發(fā)生的情況,分發(fā)到新定義的例如onSuccess()或者是 onError()中。(我也曾經(jīng)見過僅捕獲異常的引用,然后在 onPostExcecute()中進(jìn)行檢查的寫法)這最終是有點(diǎn)幫助的,但是你必須為你的每個(gè)項(xiàng)目寫上額外的代碼,隨著時(shí)間的推移,這些自定義的代碼在開發(fā)者之間和項(xiàng)目之間,可能不會(huì)保持很好的一致性和可預(yù)見性。
Activity和Fragment的生命周期
另外一個(gè)你必須面對(duì)的問題是:“當(dāng)AsyncTask正在運(yùn)行的時(shí)候,如果我退出Activity或者是旋轉(zhuǎn)設(shè)備的話會(huì)發(fā)生什么?”嗯,如果你只是 發(fā)送一些數(shù)據(jù),之后就不再關(guān)心發(fā)送結(jié)果,那可能是沒有問題的,但是如果你需要根據(jù)Task的返回結(jié)果更新UI呢?如果你不做一些事情阻止的話,那么當(dāng)你試 圖去調(diào)用Activity或者是view的話,你將得到一個(gè)空指針異常導(dǎo)致程序崩潰,因?yàn)樗麄儸F(xiàn)在是不可見或者是null的。
同樣,在這個(gè)問題上AsyncTask沒有做很多工作去幫助你。作為一個(gè)開發(fā)者,你需要確保保持一個(gè)Task的引用,并且要么當(dāng)Activity正 在被銷毀的時(shí)候取消Task,要么當(dāng)你試圖在onPostExecute()里面更新UI的時(shí)候,確保Activity是在一個(gè)可達(dá)狀態(tài)。當(dāng)你只想明確的 做一些工作,并且讓項(xiàng)目容易維護(hù)的時(shí)候,這將會(huì)繼續(xù)提高維護(hù)項(xiàng)目的難度。
旋轉(zhuǎn)時(shí)的緩存(或是其他情況)
當(dāng)你的用戶還是待在當(dāng)前Activity,僅僅是旋轉(zhuǎn)屏幕會(huì)發(fā)生什么?在這種情況下,取消Task沒有任何意義,因?yàn)樵谛D(zhuǎn)之后,你最終還是需要重 新啟動(dòng)Task?;蛘呤悄悴幌胫貑ask,因?yàn)闋顩r在一些地方以非幕等的方式發(fā)生了突變(because it mutates some state somewhere in a non-idempotent way),但是你確實(shí)想要得到結(jié)果,因?yàn)檫@樣你就可以更新UI來反映這種情況。
如果你專門的做一個(gè)只讀的加載操作,你可以使用AsyncTaskLoader去解決這個(gè)問題。但是對(duì)于標(biāo)準(zhǔn)的Android方式來說,它還是很沉重,因?yàn)槿鄙馘e(cuò)誤處理,在Activity中沒有緩存,還有很多獨(dú)有的其他怪癖。
組合的多個(gè)Web Server調(diào)用
現(xiàn)在,假如說我們已經(jīng)想辦法把上面的問題都解決了,但是我們現(xiàn)在需要做一些連續(xù)的網(wǎng)絡(luò)請(qǐng)求,每一個(gè)請(qǐng)求都需要基于前一個(gè)請(qǐng)求的結(jié)果?;蛘呤?,我們想做一些并發(fā)的網(wǎng)絡(luò)請(qǐng)求,然后把結(jié)果合并在一起發(fā)送到UI線程,但是,再次抱歉,AsyncTask在這里幫不到你。
一旦你開始做這樣的事情,隨著更多的復(fù)雜線程模型的增長(zhǎng),之前的問題會(huì)導(dǎo)致處理這樣的事情非常的痛苦和蒼白無(wú)力。如果想要這些線程一起運(yùn)行,要么你 就讓它們單獨(dú)運(yùn)行,然后回調(diào),要么讓它們?cè)谝粋€(gè)后臺(tái)線程中同步運(yùn)行,最后復(fù)制組成不同。(To chain calls together, you either keep them separate and end up in callback hell, or run them synchronously together in one background task end up duplicating work to compose them differently in other situations.)
如果要并行運(yùn)行,你必須創(chuàng)建一個(gè)自定義的executor然后傳遞給AsyncTaskTask,因?yàn)槟J(rèn)的AsyncTask不支持并行。并且為 了協(xié)調(diào)并行線程,你需要使用像是CountDownLatchs, Threads, Executors 和 Futures去降低更復(fù)雜的同步模式。
可測(cè)試性
拋開這些不說,如果你喜歡測(cè)試你的代碼,AsyncTask并不能給你帶來什么幫助。如果我們不做額外的工作,測(cè)試AsyncTask非常困難,它很脆弱并且難以維持。這是一篇有關(guān)如何成功測(cè)試AsyncTask的帖子。
更好的解決方案:RxJava’s Observable
幸運(yùn)的是,一旦我們決定使用RxJava依賴庫(kù)的時(shí)候,我們討論的這些問題就都迎刃而解了。下面我們看看它能為我們做什么。
下面我們將會(huì)使用Observables寫一個(gè)請(qǐng)求代碼來替代上面的AsyncTask方式。(如果你使用Retrofit,那么你應(yīng)該很容易使用,其次它還支持Observable 返回值,并且它工作在一個(gè)后臺(tái)的線程池,無(wú)需你額外的工作)
webService.doSomething(someData)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
result -> resultText.setText("It worked!")),
e -> handleError(e) );
錯(cuò)誤處理
你可能會(huì)注意到,沒有做額外的工作,我們已經(jīng)處理了AsyncTask不會(huì)處理的成功和錯(cuò)誤的情況,并且我們寫了很少的代碼。你看到的額外的組件是 我們想要Observer 在UI主線程中處理的結(jié)果。這樣可以讓我們前進(jìn)一點(diǎn)點(diǎn)。并且如果你的webService對(duì)象不想在后臺(tái)線程中運(yùn)行,你也可以在這里通過使 用.subscribeOn(...) 聲明。(注意:這些例子是使用Java 8的lambda語(yǔ)法,使用Retrolambda就可以在Android項(xiàng)目中進(jìn)行使用了,但在我看來,這樣做的回報(bào)是高于風(fēng)險(xiǎn)的,和寫這篇文章相比,我們更喜歡在我們的項(xiàng)目中使用。)
Activity和Fragment的生命周期
現(xiàn)在,我們想在這里利用RxAndroid解決上面提到的生命周期的問題,我們不需要指定mainThread() scheduler(順便說一句,你只需要導(dǎo)入RxAndroid)。就像下面這樣
AppObservable.bindFragment(this, webService.doSomething(someData))
.subscribe(result -> resultText.setText("It worked!")),
e -> handleError(e));
我通常的做法是在應(yīng)用的Base Fragment里面創(chuàng)建一個(gè)幫助方法來簡(jiǎn)化這一點(diǎn),你可以參考我維護(hù)的一個(gè)Base RxFragment 獲得一些指導(dǎo)。
bind(webService.doSomething(someData))
.subscribe(
result -> resultText.setText("It worked!")),
e -> handleError(e));
如果我們的Activity或者是Fragment不再是可見狀態(tài),那么AppObservable.bindFragment()可以在調(diào)用鏈中 插入一個(gè)墊片,來阻止onNext()運(yùn)行。如果當(dāng)Task試圖運(yùn)行的時(shí)候,Activity、Fragment是不可達(dá)狀態(tài),subscription 將會(huì)取消訂閱并且停止運(yùn)行,所以不會(huì)存在空指針的風(fēng)險(xiǎn),程序也不會(huì)崩潰。一個(gè)需要注意的是,當(dāng)我們離開Activity和Fragment時(shí),我們會(huì)暫時(shí) 或者是永久的泄露,這取決于問題中的Observable 的行為。所以在bind()方法中,我也會(huì)調(diào)用LifeCycleObservable機(jī)制,當(dāng)Fragment銷毀的時(shí)候自動(dòng)取消。這樣做的好處是一勞 永逸。
所以,這解決了首要的兩個(gè)問題,但是下面這一個(gè)才是RxJava大發(fā)神威的地方。
組合的多個(gè)Web Server調(diào)用
在這里我不會(huì)詳細(xì)的說明,因?yàn)檫@是一個(gè)復(fù)雜的問題, 但是通過使用Observables,你可以用非常簡(jiǎn)單和易于理解的方式完成復(fù)雜的事情。這里是一個(gè)鏈?zhǔn)絎eb Service調(diào)用的例子,這些請(qǐng)求互相依賴,在線程池中運(yùn)行第二批并行調(diào)用,然后在將結(jié)果返回給Observer之前,對(duì)數(shù)據(jù)進(jìn)行合并和排序。為了更好 的測(cè)量,我甚至在里面放置了一個(gè)過濾器。所有的業(yè)務(wù)邏輯都在下面這短短五行代碼里面...
public Observable<List<CityWeather>> getWeatherForLargeUsCapitals() {
return cityDirectory.getUsCapitals()
.flatMap(cityList -> Observable.from(cityList))
.filter(city -> city.getPopulation() > 500,000)
.flatMap(city -> weatherService.getCurrentWeather(city)) //each runs in parallel
.toSortedList((cw1,cw2) -> cw1.getCityName().compare(cw2.getCityName()));
}
旋轉(zhuǎn)時(shí)的緩存(或是其他情況)
既然這是一個(gè)加載的數(shù)據(jù),那么我們可能需要將數(shù)據(jù)進(jìn)行緩存,這樣當(dāng)我們旋轉(zhuǎn)設(shè)備的時(shí)候,就不會(huì)觸發(fā)再次調(diào)用全部web service的事件。一種實(shí)現(xiàn)的方式是保留Fragment,并且保存一個(gè)對(duì)Observable 的緩存的引用,就像下面這樣:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
weatherObservable = weatherManager.getWeatherForLargeUsCapitals().cache();
}
public void onViewCreated(...) {
super.onViewCreated(...)
bind(weatherObservable).subscribe(this);
}
在旋轉(zhuǎn)之后,訂閱者的緩存實(shí)例就會(huì)立即發(fā)出和第一次請(qǐng)求相同的請(qǐng)求,防止真實(shí)的Web Service請(qǐng)求發(fā)生。
如果你想要避免緩存的Fragment(并且有很充足的理由去避免它),我們可以通過使用AsyncSubject實(shí)現(xiàn)緩存。無(wú)論何時(shí)被訂 閱,AsyncSubject 都會(huì)重新發(fā)出最后的事件?;蛘呶覀兛梢允褂肂ehaviorSubject獲得最后的值和新值改變整個(gè)應(yīng)用程序。
WeatherListFragment.java
public void onViewCreated() {
super.onViewCreated()
bind(weatherManager.getWeatherForLargeUsCapitals()).subscribe(this);
}
WeatherManager.java
public Observable<List<CityWeather>> getWeatherForLargeUsCapitals() {
if(weatherSubject == null) {
weatherSubject = AsyncSubject.create();
cityDirectory.getUsCapitals()
.flatMap(cityList -> Observable.from(cityList))
.filter(city -> city.getPopulation() > 500,000)
.flatMap(city -> weatherService.getCurrentWeather(city))
.toSortedList((cw1,cw2) -> cw1.getCityName().compare(cw2.getCityName()))
.subscribe(weatherSubject);
}
return weatherSubject;
}
因?yàn)椤熬彺妗笔怯蒑anager單獨(dú)管理的,它不會(huì)與Fragment/Activity的周期綁定,并且在Activity/Fragment中將持續(xù)存在。如果你想強(qiáng)迫刷新基于以類似的方式來保留Fragment緩存實(shí)例的生命周期事件,你可以這樣做:
public void onCreate() {
super.onCreate()
if (savedInstanceState == null) {
weatherManager.invalidate(); //invalidate cache on fresh start
}
}
這件事情的偉大之處在于,它不像是Loaders,我們可以很靈活的緩則緩存很多Activity和Services中的結(jié)果。只需要去掉 oncreate()中的invalidate()調(diào)用,并讓你的Manager對(duì)象決定何時(shí)發(fā)出新的氣象數(shù)據(jù)就可以了??赡苁且粋€(gè)Timer,或者是用 戶定位改變,或者是其他任何時(shí)刻,這真的沒關(guān)系。你現(xiàn)在可以控制什么時(shí)候如何去更新緩存和重新加載。并且當(dāng)你的緩存策略發(fā)生改變的時(shí)候,F(xiàn)ragment 和你的Manager對(duì)象之間的接口不需要進(jìn)行改變。它只不過是一個(gè) List的Observer。
可測(cè)試性
測(cè)試是我們想要實(shí)現(xiàn)干凈、簡(jiǎn)單的最后一個(gè)挑戰(zhàn)。(讓我們忽略一個(gè)事實(shí),在測(cè)試期間,我們可能想要模擬出實(shí)際的Web服務(wù)。這樣做很簡(jiǎn)單,下面通過一個(gè)接口注入到那些依賴你可能已經(jīng)正在做的標(biāo)準(zhǔn)模式中。)
幸運(yùn)的是,Observables給我們一個(gè)簡(jiǎn)單的方式來將一個(gè)異步方法變成同步,你要做的就是使用toblocking()方法。我們看一個(gè)測(cè)試?yán)印?/p>
List results = getWeatherForLargeUsCapitals().toBlocking().first();
assertEquals(12, results.size());
就像這樣,我們沒有必要去使用Futures或者是CountDownLatchs讓做一些脆弱的操作,比如線程睡眠或者是讓我們的測(cè)試變得很復(fù)雜,我們的測(cè)試現(xiàn)在是簡(jiǎn)單、干凈、可維護(hù)的。
結(jié)論
更新:我已經(jīng)創(chuàng)建了一對(duì)簡(jiǎn)單的項(xiàng)目來演示AsyncTask風(fēng)格和AsyncTaskLoader風(fēng)格。
RxJava,你值得擁有。我們使用rx.Observable來替換AsyncTask和AsyncTaskLoader可實(shí)現(xiàn)更加強(qiáng)大和清晰的代碼。使用RxJava Observables很快樂,而且我期待能夠呈現(xiàn)更多的Android問題的解決方案。