文章轉(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àn)的具體問題時骗奖,他們并不知道如何或者是為什么要使用RxJava嫩舟。在這一系列的文章中爆安,我想要探索在我工作過的一些 依賴于RxJava架構(gòu)的Android項目中的模式叛复。
在文章的開始,我想要處理一些Android開發(fā)者在使用RxJava的時候,很容易遇到的狀況褐奥。從這個角度咖耘,我將提供更高級和更合適的解決方案。在這一系列的文章中撬码,我希望可以聽到其他開發(fā)者在使用RxJava的過程中解決類似的問題儿倒,或許他們和我發(fā)現(xiàn)的一樣呢。
問題一:后臺任務(wù)
Android開發(fā)者首先遇到的挑戰(zhàn)就是如何有效的在后臺線程中工作呜笑,然后在UI線程中更新UI夫否。這經(jīng)常是因為需要從web service中獲取數(shù)據(jù)。對于已經(jīng)有相關(guān)經(jīng)驗的你可能會說:“這有什么挑戰(zhàn)性叫胁?你只需要啟動一個AsyncTask凰慈,然后所有的工作它就都給你做了〔芴В” 如果你是這樣想的溉瓶,那么你有一個機會去改善這種狀況急鳄。這是因為你已經(jīng)習(xí)慣了這種復(fù)雜的方式并且忘記這本應(yīng)該是很簡潔的谤民,或者是說你沒有處理所有應(yīng)該處理的 邊界情況。讓我們來談?wù)勥@個疾宏。
默認(rèn)的解決方案:AsyncTask
AsyncTask是在Android里面默認(rèn)的處理工具张足,開發(fā)者可以做里面一些長時間的處理工作,而不會阻塞用戶界面坎藐。(注意:最近为牍,AsyncTaskLoader用來處理一些更加具體的數(shù)據(jù)加載任務(wù),我們以后會再談?wù)勥@個)
表面上岩馍,這似乎很簡單碉咆,你定義一些代碼在后臺線程中運行,然后定義一些代碼運行在UI線程中蛀恩,在后臺任務(wù)處理完之后疫铜,它在UI線程會處理從后臺任務(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ù)勥@個問題壳咕。
錯誤處理
這種簡單用法的第一個問題就是:“如果出現(xiàn)了錯誤怎么辦?”不幸的是顽馋,暫時沒有非常好的解決方案谓厘。所以很多的開發(fā)者最終要繼承AsyncTask, 然后在doInBackground()中包裹一個try/catch寸谜,返回一個竟稳,然后根據(jù)發(fā)生的情況,分發(fā)到新定義的例如onSuccess()或者是 onError()中。(我也曾經(jīng)見過僅捕獲異常的引用他爸,然后在 onPostExcecute()中進(jìn)行檢查的寫法)這最終是有點幫助的地啰,但是你必須為你的每個項目寫上額外的代碼,隨著時間的推移讲逛,這些自定義的代碼在開發(fā)者之間和項目之間亏吝,可能不會保持很好的一致性和可預(yù)見性。
Activity和Fragment的生命周期
另外一個你必須面對的問題是:“當(dāng)AsyncTask正在運行的時候盏混,如果我退出Activity或者是旋轉(zhuǎn)設(shè)備的話會發(fā)生什么蔚鸥?”嗯,如果你只是 發(fā)送一些數(shù)據(jù)许赃,之后就不再關(guān)心發(fā)送結(jié)果止喷,那可能是沒有問題的,但是如果你需要根據(jù)Task的返回結(jié)果更新UI呢混聊?如果你不做一些事情阻止的話弹谁,那么當(dāng)你試 圖去調(diào)用Activity或者是view的話,你將得到一個空指針異常導(dǎo)致程序崩潰句喜,因為他們現(xiàn)在是不可見或者是null的预愤。
同樣,在這個問題上AsyncTask沒有做很多工作去幫助你咳胃。作為一個開發(fā)者植康,你需要確保保持一個Task的引用,并且要么當(dāng)Activity正 在被銷毀的時候取消Task展懈,要么當(dāng)你試圖在onPostExecute()里面更新UI的時候销睁,確保Activity是在一個可達(dá)狀態(tài)。當(dāng)你只想明確的 做一些工作存崖,并且讓項目容易維護的時候冻记,這將會繼續(xù)提高維護項目的難度。
旋轉(zhuǎn)時的緩存(或是其他情況)
當(dāng)你的用戶還是待在當(dāng)前Activity来惧,僅僅是旋轉(zhuǎn)屏幕會發(fā)生什么冗栗?在這種情況下,取消Task沒有任何意義违寞,因為在旋轉(zhuǎn)之后贞瞒,你最終還是需要重 新啟動Task〕寐或者是你不想重啟Task军浆,因為狀況在一些地方以非幕等的方式發(fā)生了突變(because it mutates some state somewhere in a non-idempotent way),但是你確實想要得到結(jié)果,因為這樣你就可以更新UI來反映這種情況挡闰。
如果你專門的做一個只讀的加載操作乒融,你可以使用AsyncTaskLoader去解決這個問題掰盘。但是對于標(biāo)準(zhǔn)的Android方式來說,它還是很沉重赞季,因為缺少錯誤處理愧捕,在Activity中沒有緩存,還有很多獨有的其他怪癖申钩。
組合的多個Web Server調(diào)用
現(xiàn)在次绘,假如說我們已經(jīng)想辦法把上面的問題都解決了,但是我們現(xiàn)在需要做一些連續(xù)的網(wǎng)絡(luò)請求撒遣,每一個請求都需要基于前一個請求的結(jié)果邮偎。或者是义黎,我們想做一些并發(fā)的網(wǎng)絡(luò)請求禾进,然后把結(jié)果合并在一起發(fā)送到UI線程,但是廉涕,再次抱歉泻云,AsyncTask在這里幫不到你。
一旦你開始做這樣的事情狐蜕,隨著更多的復(fù)雜線程模型的增長宠纯,之前的問題會導(dǎo)致處理這樣的事情非常的痛苦和蒼白無力。如果想要這些線程一起運行馏鹤,要么你 就讓它們單獨運行征椒,然后回調(diào)娇哆,要么讓它們在一個后臺線程中同步運行湃累,最后復(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.)
如果要并行運行碍讨,你必須創(chuàng)建一個自定義的executor然后傳遞給AsyncTaskTask治力,因為默認(rèn)的AsyncTask不支持并行。并且為 了協(xié)調(diào)并行線程勃黍,你需要使用像是CountDownLatchs, Threads, Executors 和 Futures去降低更復(fù)雜的同步模式宵统。
可測試性
拋開這些不說,如果你喜歡測試你的代碼覆获,AsyncTask并不能給你帶來什么幫助马澈。如果我們不做額外的工作,測試AsyncTask非常困難弄息,它很脆弱并且難以維持痊班。這是一篇有關(guān)如何成功測試AsyncTask的帖子。
更好的解決方案:RxJava’s Observable
幸運的是摹量,一旦我們決定使用RxJava依賴庫的時候涤伐,我們討論的這些問題就都迎刃而解了馒胆。下面我們看看它能為我們做什么。
下面我們將會使用Observables寫一個請求代碼來替代上面的AsyncTask方式凝果。(如果你使用Retrofit祝迂,那么你應(yīng)該很容易使用,其次它還支持Observable 返回值器净,并且它工作在一個后臺的線程池型雳,無需你額外的工作)
webService.doSomething(someData)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
result -> resultText.setText("It worked!")),
e -> handleError(e) );
錯誤處理
你可能會注意到,沒有做額外的工作山害,我們已經(jīng)處理了AsyncTask不會處理的成功和錯誤的情況四啰,并且我們寫了很少的代碼。你看到的額外的組件是 我們想要Observer 在UI主線程中處理的結(jié)果粗恢。這樣可以讓我們前進(jìn)一點點柑晒。并且如果你的webService對象不想在后臺線程中運行,你也可以在這里通過使 用.subscribeOn(...) 聲明眷射。(注意:這些例子是使用Java 8的lambda語法匙赞,使用Retrolambda就可以在Android項目中進(jìn)行使用了,但在我看來妖碉,這樣做的回報是高于風(fē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)建一個幫助方法來簡化這一點,你可以參考我維護的一個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)用鏈中 插入一個墊片,來阻止onNext()運行挂绰。如果當(dāng)Task試圖運行的時候屎篱,Activity、Fragment是不可達(dá)狀態(tài)葵蒂,subscription 將會取消訂閱并且停止運行交播,所以不會存在空指針的風(fēng)險,程序也不會崩潰践付。一個需要注意的是秦士,當(dāng)我們離開Activity和Fragment時,我們會暫時 或者是永久的泄露荔仁,這取決于問題中的Observable 的行為伍宦。所以在bind()方法中芽死,我也會調(diào)用LifeCycleObservable機制,當(dāng)Fragment銷毀的時候自動取消次洼。這樣做的好處是一勞 永逸关贵。
所以,這解決了首要的兩個問題卖毁,但是下面這一個才是RxJava大發(fā)神威的地方揖曾。
組合的多個Web Server調(diào)用
在這里我不會詳細(xì)的說明,因為這是一個復(fù)雜的問題亥啦, 但是通過使用Observables炭剪,你可以用非常簡單和易于理解的方式完成復(fù)雜的事情。這里是一個鏈?zhǔn)絎eb Service調(diào)用的例子翔脱,這些請求互相依賴奴拦,在線程池中運行第二批并行調(diào)用,然后在將結(jié)果返回給Observer之前届吁,對數(shù)據(jù)進(jìn)行合并和排序错妖。為了更好 的測量,我甚至在里面放置了一個過濾器疚沐。所有的業(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ù)據(jù)暂氯,那么我們可能需要將數(shù)據(jù)進(jìn)行緩存,這樣當(dāng)我們旋轉(zhuǎn)設(shè)備的時候亮蛔,就不會觸發(fā)再次調(diào)用全部web service的事件痴施。一種實現(xiàn)的方式是保留Fragment,并且保存一個對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)之后辣吃,訂閱者的緩存實例就會立即發(fā)出和第一次請求相同的請求,防止真實的Web Service請求發(fā)生梯嗽。
如果你想要避免緩存的Fragment(并且有很充足的理由去避免它)齿尽,我們可以通過使用AsyncSubject實現(xiàn)緩存。無論何時被訂 閱灯节,AsyncSubject 都會重新發(fā)出最后的事件∶喙溃或者我們可以使用BehaviorSubject獲得最后的值和新值改變整個應(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;
}
因為“緩存”是由Manager單獨管理的,它不會與Fragment/Activity的周期綁定国裳,并且在Activity/Fragment中將持續(xù)存在形入。如果你想強迫刷新基于以類似的方式來保留Fragment緩存實例的生命周期事件,你可以這樣做:
public void onCreate() {
super.onCreate()
if (savedInstanceState == null) {
weatherManager.invalidate(); //invalidate cache on fresh start
}
}
這件事情的偉大之處在于缝左,它不像是Loaders亿遂,我們可以很靈活的緩則緩存很多Activity和Services中的結(jié)果浓若。只需要去掉 oncreate()中的invalidate()調(diào)用,并讓你的Manager對象決定何時發(fā)出新的氣象數(shù)據(jù)就可以了蛇数∨驳觯可能是一個Timer,或者是用 戶定位改變耳舅,或者是其他任何時刻碌上,這真的沒關(guān)系。你現(xiàn)在可以控制什么時候如何去更新緩存和重新加載浦徊。并且當(dāng)你的緩存策略發(fā)生改變的時候馏予,F(xiàn)ragment 和你的Manager對象之間的接口不需要進(jìn)行改變。它只不過是一個 List的Observer盔性。
可測試性
測試是我們想要實現(xiàn)干凈霞丧、簡單的最后一個挑戰(zhàn)。(讓我們忽略一個事實冕香,在測試期間,我們可能想要模擬出實際的Web服務(wù)蚯妇。這樣做很簡單,下面通過一個接口注入到那些依賴你可能已經(jīng)正在做的標(biāo)準(zhǔn)模式中暂筝。)
幸運的是箩言,Observables給我們一個簡單的方式來將一個異步方法變成同步,你要做的就是使用toblocking()方法焕襟。我們看一個測試?yán)印?/p>
List results = getWeatherForLargeUsCapitals().toBlocking().first();
assertEquals(12, results.size());
就像這樣陨收,我們沒有必要去使用Futures或者是CountDownLatchs讓做一些脆弱的操作,比如線程睡眠或者是讓我們的測試變得很復(fù)雜鸵赖,我們的測試現(xiàn)在是簡單务漩、干凈、可維護的它褪。
結(jié)論
更新:我已經(jīng)創(chuàng)建了一對簡單的項目來演示AsyncTask風(fēng)格和AsyncTaskLoader風(fēng)格饵骨。
RxJava,你值得擁有茫打。我們使用rx.Observable來替換AsyncTask和AsyncTaskLoader可實現(xiàn)更加強大和清晰的代碼居触。使用RxJava Observables很快樂,而且我期待能夠呈現(xiàn)更多的Android問題的解決方案老赤。