在第1,2,3篇中,我大概介紹了RxJava是怎麼使用的。下面我會介紹如何在Android中使用RxJava.
RxAndroid
RxAndroid是RxJava的一個針對Android平臺的擴充套件。它包含了一些能夠簡化Android開發的工具。
首先,AndroidSchedulers提供了針對Android的執行緒系統的排程器。需要在UI執行緒中執行某些程式碼?很簡單,只需要使用AndroidSchedulers.mainThread():
1 2 3 4 |
retrofitService.getImage(url) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap)); |
如果你已經建立了自己的Handler,你可以使用HandlerThreadScheduler1將一個排程器連結到你的handler上。
接著要介紹的就是AndroidObservable,它提供了跟多的功能來配合Android的生命週期。bindActivity()和bindFragment()方法預設使用AndroidSchedulers.mainThread()來執行觀察者程式碼,這兩個方法會在Activity或者Fragment結束的時候通知被觀察者停止發出新的訊息。
1 2 3 |
AndroidObservable.bindActivity(this, retrofitService.getImage(url)) .subscribeOn(Schedulers.io()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap); |
我自己也很喜歡AndroidObservable.fromBroadcast()方法,它允許你建立一個類似BroadcastReceiver的Observable物件。下面的例子展示瞭如何在網路變化的時候被通知到:
1 2 3 |
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); AndroidObservable.fromBroadcast(context, filter) .subscribe(intent -> handleConnectivityChange(intent)); |
最後要介紹的是ViewObservable,使用它可以給View新增了一些繫結。如果你想在每次點選view的時候都收到一個事件,可以使用ViewObservable.clicks(),或者你想監聽TextView的內容變化,可以使用ViewObservable.text()。
1 2 |
ViewObservable.clicks(mCardNameEditText, false) .subscribe(view -> handleClick(view)); |
Retrofit
大名鼎鼎的Retrofit庫內建了對RxJava的支援。通常呼叫發可以通過使用一個Callback物件來獲取非同步的結果:
1 2 |
@GET("/user/{id}/photo") void getUserPhoto(@Path("id") int id, Callback<Photo> cb); |
使用RxJava,你可以直接返回一個Observable物件。
1 2 |
@GET("/user/{id}/photo") Observable<Photo> getUserPhoto(@Path("id") int id); |
現在你可以隨意使用Observable物件了。你不僅可以獲取資料,還可以進行變換。
Retrofit對Observable的支援使得它可以很簡單的將多個REST請求結合起來。比如我們有一個請求是獲取照片的,還有一個請求是獲取後設資料的,我們就可以將這兩個請求併發的發出,並且等待兩個結果都返回之後再做處理:
1 2 3 4 5 |
Observable.zip( service.getUserPhoto(id), service.getPhotoMetadata(id), (photo, metadata) -> createPhotoWithData(photo, metadata)) .subscribe(photoWithData -> showPhoto(photoWithData)); |
在第二篇裡我展示過一個類似的例子(使用flatMap())。這裡我只是想展示以下使用RxJava+Retrofit可以多麼簡單地組合多個REST請求。
遺留程式碼,執行極慢的程式碼
Retrofit可以返回Observable物件,但是如果你使用的別的庫並不支援這樣怎麼辦?或者說一個內部的內碼,你想把他們轉換成Observable的?有什麼簡單的辦法沒?
絕大多數時候Observable.just() 和 Observable.from() 能夠幫助你從遺留程式碼中建立 Observable 物件:
1 2 3 4 5 |
private Object oldMethod() { ... } public Observable<Object> newMethod() { return Observable.just(oldMethod()); } |
上面的例子中如果oldMethod()足夠快是沒有什麼問題的,但是如果很慢呢?呼叫oldMethod()將會阻塞住他所在的執行緒。
為了解決這個問題,可以參考我一直使用的方法–使用defer()來包裝緩慢的程式碼:
1 2 3 4 5 |
private Object slowBlockingMethod() { ... } public Observable<Object> newMethod() { return Observable.defer(() -> Observable.just(slowBlockingMethod())); } |
現在,newMethod()的呼叫不會阻塞了,除非你訂閱返回的observable物件。
生命週期
我把最難的不分留在了最後。如何處理Activity的生命週期?主要就是兩個問題:
1.在configuration改變(比如轉屏)之後繼續之前的Subscription。
比如你使用Retrofit發出了一個REST請求,接著想在listview中展示結果。如果在網路請求的時候使用者旋轉了螢幕怎麼辦?你當然想繼續剛才的請求,但是怎麼搞?
2.Observable持有Context導致的記憶體洩露
這個問題是因為建立subscription的時候,以某種方式持有了context的引用,尤其是當你和view互動的時候,這太容易發生!如果Observable沒有及時結束,記憶體佔用就會越來越大。
不幸的是,沒有銀彈來解決這兩個問題,但是這裡有一些指導方案你可以參考。
第一個問題的解決方案就是使用RxJava內建的快取機制,這樣你就可以對同一個Observable物件執行unsubscribe/resubscribe,卻不用重複執行得到Observable的程式碼。cache() (或者 replay())會繼續執行網路請求(甚至你呼叫了unsubscribe也不會停止)。這就是說你可以在Activity重新建立的時候從cache()的返回值中建立一個新的Observable物件。
1 2 3 4 5 6 7 8 |
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是使用的同一個快取的請求。當然在哪裡去儲存請求的結果還是要你自己來做,和所有其他的生命週期相關的解決方案一延虎,必須在生命週期外的某個地方儲存。(retained fragment或者單例等等)。
第二個問題的解決方案就是在生命週期的某個時刻取消訂閱。一個很常見的模式就是使用CompositeSubscription來持有所有的Subscriptions,然後在onDestroy()或者onDestroyView()裡取消所有的訂閱。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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的基類裡建立一個CompositeSubscription物件,在子類中使用它。
注意! 一旦你呼叫了 CompositeSubscription.unsubscribe(),這個CompositeSubscription物件就不可用了, 如果你還想使用CompositeSubscription,就必須在建立一個新的物件了。
兩個問題的解決方案都需要新增額外的程式碼,如果誰有更好的方案,歡迎告訴我。
總結
RxJava還是一個很新的專案,RxAndroid更是。RxAndroid目前還在活躍開發中,也沒有多少好的例子。我打賭一年之後我的一些建議就會被看做過時了。