http://blog.csdn.net/lzyzsd/article/details/45033611
在第1,2,3篇中,我大概介紹了RxJava是怎麼使用的。下面我會介紹如何在Android中使用RxJava.
RxAndroid
RxAndroid是RxJava的一個針對Android平臺的擴展。它包含了一些能夠簡化Android開發的工具。
首先,AndroidSchedulers提供了針對Android的線程系統的調度器。需要在UI線程中運行某些代碼?很簡單,只需要使用AndroidSchedulers.mainThread():
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">retrofitService.getImage(url) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>
如果你已經創建了自己的Handler,你可以使用HandlerThreadScheduler1將一個調度器鏈接到你的handler上。
接着要介紹的就是AndroidObservable,它提供了跟多的功能來配合Android的生命週期。bindActivity()和bindFragment()方法默認使用AndroidSchedulers.mainThread()來執行觀察者代碼,這兩個方法會在Activity或者Fragment結束的時候通知被觀察者停止發出新的消息。
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">AndroidObservable.bindActivity(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>, retrofitService.getImage(url)) .subscribeOn(Schedulers.io()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>
我自己也很喜歡AndroidObservable.fromBroadcast()方法,它允許你創建一個類似BroadcastReceiver的Observable對象。下面的例子展示瞭如何在網絡變化的時候被通知到:
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">IntentFilter filter = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); AndroidObservable.fromBroadcast(context, filter) .subscribe(intent -> handleConnectivityChange(intent));</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>
最後要介紹的是ViewObservable,使用它可以給View添加了一些綁定。如果你想在每次點擊view的時候都收到一個事件,可以使用ViewObservable.clicks(),或者你想監聽TextView的內容變化,可以使用ViewObservable.text()。
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">ViewObservable.clicks(mCardNameEditText, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>) .subscribe(view -> handleClick(view));</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>
Retrofit
大名鼎鼎的Retrofit庫內置了對RxJava的支持。通常調用發可以通過使用一個Callback對象來獲取異步的結果:
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@GET</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"/user/{id}/photo"</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> getUserPhoto(<span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Path</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"id"</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> id, Callback<Photo> cb);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>
使用RxJava,你可以直接返回一個Observable對象。
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@GET</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"/user/{id}/photo"</span>) Observable<Photo> getUserPhoto(<span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Path</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"id"</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> id);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>
現在你可以隨意使用Observable對象了。你不僅可以獲取數據,還可以進行變換。
Retrofit對Observable的支持使得它可以很簡單的將多個REST請求結合起來。比如我們有一個請求是獲取照片的,還有一個請求是獲取元數據的,我們就可以將這兩個請求併發的發出,並且等待兩個結果都返回之後再做處理:
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">Observable.zip( service.getUserPhoto(id), service.getPhotoMetadata(id), (photo, metadata) -> createPhotoWithData(photo, metadata)) .subscribe(photoWithData -> showPhoto(photoWithData));</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>
在第二篇裏我展示過一個類似的例子(使用flatMap())。這裏我只是想展示以下使用RxJava+Retrofit可以多麼簡單地組合多個REST請求。
遺留代碼,運行極慢的代碼
Retrofit可以返回Observable對象,但是如果你使用的別的庫並不支持這樣怎麼辦?或者說一個內部的內碼,你想把他們轉換成Observable的?有什麼簡單的辦法沒?
絕大多數時候Observable.just() 和 Observable.from() 能夠幫助你從遺留代碼中創建 Observable 對象:
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> Object <span class="hljs-title" style="box-sizing: border-box;">oldMethod</span>() { ... } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> Observable<Object> <span class="hljs-title" style="box-sizing: border-box;">newMethod</span>() { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> Observable.just(oldMethod()); }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>
上面的例子中如果oldMethod()足夠快是沒有什麼問題的,但是如果很慢呢?調用oldMethod()將會阻塞住他所在的線程。
爲了解決這個問題,可以參考我一直使用的方法–使用defer()來包裝緩慢的代碼:
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> Object <span class="hljs-title" style="box-sizing: border-box;">slowBlockingMethod</span>() { ... } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> Observable<Object> <span class="hljs-title" style="box-sizing: border-box;">newMethod</span>() { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> Observable.defer(() -> Observable.just(slowBlockingMethod())); }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>
現在,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對象。
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">Observable<Photo> request = service.getUserPhoto(id).cache(); Subscription sub = request.subscribe(photo -> handleUserPhoto(photo)); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// ...When the Activity is being recreated...</span> sub.unsubscribe(); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// ...Once the Activity is recreated...</span> request.subscribe(photo -> handleUserPhoto(photo));</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>
注意,兩次sub是使用的同一個緩存的請求。當然在哪裏去存儲請求的結果還是要你自己來做,和所有其他的生命週期相關的解決方案一延虎,必須在生命週期外的某個地方存儲。(retained fragment或者單例等等)。
第二個問題的解決方案就是在生命週期的某個時刻取消訂閱。一個很常見的模式就是使用CompositeSubscription來持有所有的Subscriptions,然後在onDestroy()或者onDestroyView()裏取消所有的訂閱。
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> CompositeSubscription mCompositeSubscription = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> CompositeSubscription(); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">doSomething</span>() { mCompositeSubscription.add( AndroidObservable.bindActivity(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>, Observable.just(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Hello, World!"</span>)) .subscribe(s -> System.out.println(s))); } <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onDestroy</span>() { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onDestroy(); mCompositeSubscription.unsubscribe(); }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li></ul>
你可以在Activity/Fragment的基類裏創建一個CompositeSubscription對象,在子類中使用它。
注意! 一旦你調用了 CompositeSubscription.unsubscribe(),這個CompositeSubscription對象就不可用了, 如果你還想使用CompositeSubscription,就必須在創建一個新的對象了。
兩個問題的解決方案都需要添加額外的代碼,如果誰有更好的方案,歡迎告訴我。
總結
RxJava還是一個很新的項目,RxAndroid更是。RxAndroid目前還在活躍開發中,也沒有多少好的例子。我打賭一年之後我的一些建議就會被看做過時了。