Android中通過RxJava進(jìn)行響應(yīng)式程序設(shè)計(jì)的入門指南
錯(cuò)誤處理
到目前為止,我們都沒怎么介紹onComplete()和onError()函數(shù)。這兩個(gè)函數(shù)用來通知訂閱者,被觀察的對(duì)象將停止發(fā)送數(shù)據(jù)以及為什么停止(成功的完成或者出錯(cuò)了)。
下面的代碼展示了怎么使用這兩個(gè)函數(shù):
Observable.just("Hello, world!") .map(s -> potentialException(s)) .map(s -> anotherPotentialException(s)) .subscribe(new Subscriber<String>() { @Override public void onNext(String s) { System.out.println(s); } @Override public void onCompleted() { System.out.println("Completed!"); } @Override public void onError(Throwable e) { System.out.println("Ouch!"); } });
代碼中的potentialException() 和 anotherPotentialException()有可能會(huì)拋出異常。每一個(gè)Observerable對(duì)象在終結(jié)的時(shí)候都會(huì)調(diào)用onCompleted()或者onError()方法,所以Demo中會(huì)打印”Completed!”或者”O(jiān)uch!”。
這種模式有以下幾個(gè)優(yōu)點(diǎn):
1.只要有異常發(fā)生onError()一定會(huì)被調(diào)用
這極大的簡(jiǎn)化了錯(cuò)誤處理。只需要在一個(gè)地方處理錯(cuò)誤即可以。
2.操作符不需要處理異常
將異常處理交給訂閱者來做,Observerable的操作符調(diào)用鏈中一旦有一個(gè)拋出了異常,就會(huì)直接執(zhí)行onError()方法。
3.你能夠知道什么時(shí)候訂閱者已經(jīng)接收了全部的數(shù)據(jù)。
知道什么時(shí)候任務(wù)結(jié)束能夠幫助簡(jiǎn)化代碼的流程。(雖然有可能Observable對(duì)象永遠(yuǎn)不會(huì)結(jié)束)
我覺得這種錯(cuò)誤處理方式比傳統(tǒng)的錯(cuò)誤處理更簡(jiǎn)單。傳統(tǒng)的錯(cuò)誤處理中,通常是在每個(gè)回調(diào)中處理錯(cuò)誤。這不僅導(dǎo)致了重復(fù)的代碼,并且意味著每個(gè)回調(diào)都必須知道如何處理錯(cuò)誤,你的回調(diào)代碼將和調(diào)用者緊耦合在一起。
使用RxJava,Observable對(duì)象根本不需要知道如何處理錯(cuò)誤!操作符也不需要處理錯(cuò)誤狀態(tài)-一旦發(fā)生錯(cuò)誤,就會(huì)跳過當(dāng)前和后續(xù)的操作符。所有的錯(cuò)誤處理都交給訂閱者來做。
調(diào)度器
假設(shè)你編寫的Android app需要從網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)(感覺這是必備的了,還有單機(jī)么?)。網(wǎng)絡(luò)請(qǐng)求需要花費(fèi)較長(zhǎng)的時(shí)間,因此你打算在另外一個(gè)線程中加載數(shù)據(jù)。那么問題來了!
編寫多線程的Android應(yīng)用程序是很難的,因?yàn)槟惚仨毚_保代碼在正確的線程中運(yùn)行,否則的話可能會(huì)導(dǎo)致app崩潰。最常見的就是在非主線程更新UI。
使用RxJava,你可以使用subscribeOn()指定觀察者代碼運(yùn)行的線程,使用observerOn()指定訂閱者運(yùn)行的線程:
myObservableServices.retrieveImage(url) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));
是不是很簡(jiǎn)單?任何在我的Subscriber前面執(zhí)行的代碼都是在I/O線程中運(yùn)行。最后,操作view的代碼在主線程中運(yùn)行.
最棒的是我可以把subscribeOn()和observerOn()添加到任何Observable對(duì)象上。這兩個(gè)也是操作符!。我不需要關(guān)心Observable對(duì)象以及它上面有哪些操作符。僅僅運(yùn)用這兩個(gè)操作符就可以實(shí)現(xiàn)在不同的線程中調(diào)度。
如果使用AsyncTask或者其他類似的,我將不得不仔細(xì)設(shè)計(jì)我的代碼,找出需要并發(fā)執(zhí)行的部分。使用RxJava,我可以保持代碼不變,僅僅在需要并發(fā)的時(shí)候調(diào)用這兩個(gè)操作符就可以。
訂閱(Subscriptions)
當(dāng)調(diào)用Observable.subscribe(),會(huì)返回一個(gè)Subscription對(duì)象。這個(gè)對(duì)象代表了被觀察者和訂閱者之間的聯(lián)系。
ubscription subscription = Observable.just("Hello, World!") .subscribe(s -> System.out.println(s));
你可以在后面使用這個(gè)Subscription對(duì)象來操作被觀察者和訂閱者之間的聯(lián)系.
subscription.unsubscribe(); System.out.println("Unsubscribed=" + subscription.isUnsubscribed()); // Outputs "Unsubscribed=true"
RxJava的另外一個(gè)好處就是它處理unsubscribing的時(shí)候,會(huì)停止整個(gè)調(diào)用鏈。如果你使用了一串很復(fù)雜的操作符,調(diào)用unsubscribe將會(huì)在他當(dāng)前執(zhí)行的地方終止。不需要做任何額外的工作!
RxAndroid
RxAndroid是RxJava的一個(gè)針對(duì)Android平臺(tái)的擴(kuò)展。它包含了一些能夠簡(jiǎn)化Android開發(fā)的工具。
首先,AndroidSchedulers提供了針對(duì)Android的線程系統(tǒng)的調(diào)度器。需要在UI線程中運(yùn)行某些代碼?很簡(jiǎn)單,只需要使用AndroidSchedulers.mainThread():
retrofitService.getImage(url) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));
如果你已經(jīng)創(chuàng)建了自己的Handler,你可以使用HandlerThreadScheduler1將一個(gè)調(diào)度器鏈接到你的handler上。
接著要介紹的就是AndroidObservable,它提供了跟多的功能來配合Android的生命周期。bindActivity()和bindFragment()方法默認(rèn)使用AndroidSchedulers.mainThread()來執(zhí)行觀察者代碼,這兩個(gè)方法會(huì)在Activity或者Fragment結(jié)束的時(shí)候通知被觀察者停止發(fā)出新的消息。
AndroidObservable.bindActivity(this, retrofitService.getImage(url)) .subscribeOn(Schedulers.io()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap);
我自己也很喜歡AndroidObservable.fromBroadcast()方法,它允許你創(chuàng)建一個(gè)類似BroadcastReceiver的Observable對(duì)象。下面的例子展示了如何在網(wǎng)絡(luò)變化的時(shí)候被通知到:
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); AndroidObservable.fromBroadcast(context, filter) .subscribe(intent -> handleConnectivityChange(intent));
最后要介紹的是ViewObservable,使用它可以給View添加了一些綁定。如果你想在每次點(diǎn)擊view的時(shí)候都收到一個(gè)事件,可以使用ViewObservable.clicks(),或者你想監(jiān)聽TextView的內(nèi)容變化,可以使用ViewObservable.text()。
ViewObservable.clicks(mCardNameEditText, false) .subscribe(view -> handleClick(view));
Retrofit
大名鼎鼎的Retrofit庫(kù)內(nèi)置了對(duì)RxJava的支持(官方下載頁(yè)http://square.github.io/retrofit/#download)。通常調(diào)用發(fā)可以通過使用一個(gè)Callback對(duì)象來獲取異步的結(jié)果:
@GET("/user/{id}/photo") void getUserPhoto(@Path("id") int id, Callback<Photo> cb);
使用RxJava,你可以直接返回一個(gè)Observable對(duì)象。
@GET("/user/{id}/photo") Observable<Photo> getUserPhoto(@Path("id") int id);
現(xiàn)在你可以隨意使用Observable對(duì)象了。你不僅可以獲取數(shù)據(jù),還可以進(jìn)行變換。
Retrofit對(duì)Observable的支持使得它可以很簡(jiǎn)單的將多個(gè)REST請(qǐng)求結(jié)合起來。比如我們有一個(gè)請(qǐng)求是獲取照片的,還有一個(gè)請(qǐng)求是獲取元數(shù)據(jù)的,我們就可以將這兩個(gè)請(qǐng)求并發(fā)的發(fā)出,并且等待兩個(gè)結(jié)果都返回之后再做處理:
Observable.zip( service.getUserPhoto(id), service.getPhotoMetadata(id), (photo, metadata) -> createPhotoWithData(photo, metadata)) .subscribe(photoWithData -> showPhoto(photoWithData));
在第二篇里我展示過一個(gè)類似的例子(使用flatMap())。這里我只是想展示以下使用RxJava+Retrofit可以多么簡(jiǎn)單地組合多個(gè)REST請(qǐng)求。
遺留代碼,運(yùn)行極慢的代碼
Retrofit可以返回Observable對(duì)象,但是如果你使用的別的庫(kù)并不支持這樣怎么辦?或者說一個(gè)內(nèi)部的內(nèi)碼,你想把他們轉(zhuǎn)換成Observable的?有什么簡(jiǎn)單的辦法沒?
絕大多數(shù)時(shí)候Observable.just() 和 Observable.from() 能夠幫助你從遺留代碼中創(chuàng)建 Observable 對(duì)象:
private Object oldMethod() { ... } public Observable<Object> newMethod() { return Observable.just(oldMethod()); }
上面的例子中如果oldMethod()足夠快是沒有什么問題的,但是如果很慢呢?調(diào)用oldMethod()將會(huì)阻塞住他所在的線程。
為了解決這個(gè)問題,可以參考我一直使用的方法–使用defer()來包裝緩慢的代碼:
private Object slowBlockingMethod() { ... } public Observable<Object> newMethod() { return Observable.defer(() -> Observable.just(slowBlockingMethod())); }
現(xiàn)在,newMethod()的調(diào)用不會(huì)阻塞了,除非你訂閱返回的observable對(duì)象。
生命周期
我把最難的不分留在了最后。如何處理Activity的生命周期?主要就是兩個(gè)問題:
1.在configuration改變(比如轉(zhuǎn)屏)之后繼續(xù)之前的Subscription。
比如你使用Retrofit發(fā)出了一個(gè)REST請(qǐng)求,接著想在listview中展示結(jié)果。如果在網(wǎng)絡(luò)請(qǐng)求的時(shí)候用戶旋轉(zhuǎn)了屏幕怎么辦?你當(dāng)然想繼續(xù)剛才的請(qǐng)求,但是怎么搞?
2.Observable持有Context導(dǎo)致的內(nèi)存泄露
這個(gè)問題是因?yàn)閯?chuàng)建subscription的時(shí)候,以某種方式持有了context的引用,尤其是當(dāng)你和view交互的時(shí)候,這太容易發(fā)生!如果Observable沒有及時(shí)結(jié)束,內(nèi)存占用就會(huì)越來越大。
不幸的是,沒有銀彈來解決這兩個(gè)問題,但是這里有一些指導(dǎo)方案你可以參考。
第一個(gè)問題的解決方案就是使用RxJava內(nèi)置的緩存機(jī)制,這樣你就可以對(duì)同一個(gè)Observable對(duì)象執(zhí)行unsubscribe/resubscribe,卻不用重復(fù)運(yùn)行得到Observable的代碼。cache() (或者 replay())會(huì)繼續(xù)執(zhí)行網(wǎng)絡(luò)請(qǐng)求(甚至你調(diào)用了unsubscribe也不會(huì)停止)。這就是說你可以在Activity重新創(chuàng)建的時(shí)候從cache()的返回值中創(chuàng)建一個(gè)新的Observable對(duì)象。
Observable<Photo> request = service.getUserPhoto(id).cache(); Subscription sub = request.subscribe(photo -> handleUserPhoto(photo)); // ...When the Activity is being recreated... sub.unsubscribe(); // ...Once the Activity is recreated... request.subscribe(photo -> handleUserPhoto(photo));
注意,兩次sub是使用的同一個(gè)緩存的請(qǐng)求。當(dāng)然在哪里去存儲(chǔ)請(qǐng)求的結(jié)果還是要你自己來做,和所有其他的生命周期相關(guān)的解決方案一延虎,必須在生命周期外的某個(gè)地方存儲(chǔ)。(retained fragment或者單例等等)。
第二個(gè)問題的解決方案就是在生命周期的某個(gè)時(shí)刻取消訂閱。一個(gè)很常見的模式就是使用CompositeSubscription來持有所有的Subscriptions,然后在onDestroy()或者onDestroyView()里取消所有的訂閱。
private CompositeSubscription mCompositeSubscription = new CompositeSubscription(); private void doSomething() { mCompositeSubscription.add( AndroidObservable.bindActivity(this, Observable.just("Hello, World!")) .subscribe(s -> System.out.println(s))); } @Override protected void onDestroy() { super.onDestroy(); mCompositeSubscription.unsubscribe(); }
你可以在Activity/Fragment的基類里創(chuàng)建一個(gè)CompositeSubscription對(duì)象,在子類中使用它。
注意! 一旦你調(diào)用了 CompositeSubscription.unsubscribe(),這個(gè)CompositeSubscription對(duì)象就不可用了, 如果你還想使用CompositeSubscription,就必須在創(chuàng)建一個(gè)新的對(duì)象了。
- Java 程序設(shè)計(jì)總復(fù)習(xí)題(java基礎(chǔ)代碼)
- Java Swing程序設(shè)計(jì)實(shí)戰(zhàn)
- Java面向?qū)ο蟪绦蛟O(shè)計(jì):類的定義,靜態(tài)變量,成員變量,構(gòu)造函數(shù),封裝與私有,this概念與用法詳解
- Java面向?qū)ο蟪绦蛟O(shè)計(jì):繼承,多態(tài)用法實(shí)例分析
- Java面向?qū)ο蟪绦蛟O(shè)計(jì):抽象類,接口用法實(shí)例分析
- java程序設(shè)計(jì)語(yǔ)言的優(yōu)勢(shì)及特點(diǎn)
- java門禁系統(tǒng)面向?qū)ο蟪绦蛟O(shè)計(jì)
- Java網(wǎng)絡(luò)編程之TCP程序設(shè)計(jì)
- Java面向?qū)ο蟪绦蛟O(shè)計(jì)多態(tài)性示例
- Java程序設(shè)計(jì)之12個(gè)經(jīng)典樣例
相關(guān)文章
Android編程實(shí)現(xiàn)獲取新浪天氣預(yù)報(bào)數(shù)據(jù)的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)獲取新浪天氣預(yù)報(bào)數(shù)據(jù)的方法,涉及Android基于新浪接口的調(diào)用及數(shù)據(jù)處理技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11Android實(shí)現(xiàn)自定義標(biāo)題欄的方法
這篇文章主要介紹了Android實(shí)現(xiàn)自定義標(biāo)題欄的方法,需要的朋友可以參考下2015-12-12詳解Android App中ViewPager使用PagerAdapter的方法
這篇文章主要介紹了詳解Android App中ViewPager使用PagerAdapter的方法,同時(shí)附帶了一個(gè)ViewPager的PagerAdapter不能更新數(shù)據(jù)的問題解決方法,需要的朋友可以參考下2016-03-03Android利用ContentProvider初始化組件的踩坑記錄
做Android SDK開發(fā)的時(shí)候,一般我們會(huì)將初始化的方法封裝,然后讓調(diào)用SDK的開發(fā)者在Application的onCreate方法中進(jìn)行初始化,下面這篇文章主要給大家介紹了關(guān)于Android利用ContentProvider初始化組件的踩坑記錄,需要的朋友可以參考下2022-04-04Android實(shí)現(xiàn)兩圓點(diǎn)之間來回移動(dòng)加載進(jìn)度
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)兩圓點(diǎn)之間來回移動(dòng)加載進(jìn)度,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06Android實(shí)現(xiàn)帶列表的地圖POI周邊搜索功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)帶列表的地圖POI周邊搜索功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05Android使用Intent傳大數(shù)據(jù)簡(jiǎn)單實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Android使用Intent傳大數(shù)據(jù)簡(jiǎn)單實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Android Vitamio和ExoPlayer兩種播放器優(yōu)劣分析
Vitamio和ExoPlayer都是用于安卓平臺(tái)的視頻播放器庫(kù),它們各有優(yōu)缺點(diǎn),具體使用哪一個(gè),需要根據(jù)你的實(shí)際需求、開發(fā)經(jīng)驗(yàn)、項(xiàng)目規(guī)模等多個(gè)因素綜合考慮2023-04-04