編輯:關於Android編程
錯誤處理
到目前為止,我們都沒怎麼介紹onComplete()和onError()函數。這兩個函數用來通知訂閱者,被觀察的對象將停止發送數據以及為什麼停止(成功的完成或者出錯了)。
下面的代碼展示了怎麼使用這兩個函數:
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()有可能會拋出異常。每一個Observerable對象在終結的時候都會調用onCompleted()或者onError()方法,所以Demo中會打印”Completed!”或者”Ouch!”。
這種模式有以下幾個優點:
1.只要有異常發生onError()一定會被調用
這極大的簡化了錯誤處理。只需要在一個地方處理錯誤即可以。
2.操作符不需要處理異常
將異常處理交給訂閱者來做,Observerable的操作符調用鏈中一旦有一個拋出了異常,就會直接執行onError()方法。
3.你能夠知道什麼時候訂閱者已經接收了全部的數據。
知道什麼時候任務結束能夠幫助簡化代碼的流程。(雖然有可能Observable對象永遠不會結束)
我覺得這種錯誤處理方式比傳統的錯誤處理更簡單。傳統的錯誤處理中,通常是在每個回調中處理錯誤。這不僅導致了重復的代碼,並且意味著每個回調都必須知道如何處理錯誤,你的回調代碼將和調用者緊耦合在一起。
使用RxJava,Observable對象根本不需要知道如何處理錯誤!操作符也不需要處理錯誤狀態-一旦發生錯誤,就會跳過當前和後續的操作符。所有的錯誤處理都交給訂閱者來做。
調度器
假設你編寫的Android app需要從網絡請求數據(感覺這是必備的了,還有單機麼?)。網絡請求需要花費較長的時間,因此你打算在另外一個線程中加載數據。那麼問題來了!
編寫多線程的Android應用程序是很難的,因為你必須確保代碼在正確的線程中運行,否則的話可能會導致app崩潰。最常見的就是在非主線程更新UI。
使用RxJava,你可以使用subscribeOn()指定觀察者代碼運行的線程,使用observerOn()指定訂閱者運行的線程:
myObservableServices.retrieveImage(url) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));
是不是很簡單?任何在我的Subscriber前面執行的代碼都是在I/O線程中運行。最後,操作view的代碼在主線程中運行.
最棒的是我可以把subscribeOn()和observerOn()添加到任何Observable對象上。這兩個也是操作符!。我不需要關心Observable對象以及它上面有哪些操作符。僅僅運用這兩個操作符就可以實現在不同的線程中調度。
如果使用AsyncTask或者其他類似的,我將不得不仔細設計我的代碼,找出需要並發執行的部分。使用RxJava,我可以保持代碼不變,僅僅在需要並發的時候調用這兩個操作符就可以。
訂閱(Subscriptions)
當調用Observable.subscribe(),會返回一個Subscription對象。這個對象代表了被觀察者和訂閱者之間的聯系。
ubscription subscription = Observable.just("Hello, World!") .subscribe(s -> System.out.println(s));
你可以在後面使用這個Subscription對象來操作被觀察者和訂閱者之間的聯系.
subscription.unsubscribe(); System.out.println("Unsubscribed=" + subscription.isUnsubscribed()); // Outputs "Unsubscribed=true"
RxJava的另外一個好處就是它處理unsubscribing的時候,會停止整個調用鏈。如果你使用了一串很復雜的操作符,調用unsubscribe將會在他當前執行的地方終止。不需要做任何額外的工作!
RxAndroid
RxAndroid是RxJava的一個針對Android平台的擴展。它包含了一些能夠簡化Android開發的工具。
首先,AndroidSchedulers提供了針對Android的線程系統的調度器。需要在UI線程中運行某些代碼?很簡單,只需要使用AndroidSchedulers.mainThread():
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結束的時候通知被觀察者停止發出新的消息。
AndroidObservable.bindActivity(this, retrofitService.getImage(url)) .subscribeOn(Schedulers.io()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap);
我自己也很喜歡AndroidObservable.fromBroadcast()方法,它允許你創建一個類似BroadcastReceiver的Observable對象。下面的例子展示了如何在網絡變化的時候被通知到:
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); AndroidObservable.fromBroadcast(context, filter) .subscribe(intent -> handleConnectivityChange(intent));
最後要介紹的是ViewObservable,使用它可以給View添加了一些綁定。如果你想在每次點擊view的時候都收到一個事件,可以使用ViewObservable.clicks(),或者你想監聽TextView的內容變化,可以使用ViewObservable.text()。
ViewObservable.clicks(mCardNameEditText, false) .subscribe(view -> handleClick(view));
Retrofit
大名鼎鼎的Retrofit庫內置了對RxJava的支持(官方下載頁http://square.github.io/retrofit/#download)。通常調用發可以通過使用一個Callback對象來獲取異步的結果:
@GET("/user/{id}/photo") void getUserPhoto(@Path("id") int id, Callback<Photo> cb);
使用RxJava,你可以直接返回一個Observable對象。
@GET("/user/{id}/photo") Observable<Photo> getUserPhoto(@Path("id") int id);
現在你可以隨意使用Observable對象了。你不僅可以獲取數據,還可以進行變換。
Retrofit對Observable的支持使得它可以很簡單的將多個REST請求結合起來。比如我們有一個請求是獲取照片的,還有一個請求是獲取元數據的,我們就可以將這兩個請求並發的發出,並且等待兩個結果都返回之後再做處理:
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 對象:
private Object oldMethod() { ... } public Observable<Object> newMethod() { return Observable.just(oldMethod()); }
上面的例子中如果oldMethod()足夠快是沒有什麼問題的,但是如果很慢呢?調用oldMethod()將會阻塞住他所在的線程。
為了解決這個問題,可以參考我一直使用的方法–使用defer()來包裝緩慢的代碼:
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對象。
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()裡取消所有的訂閱。
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,就必須在創建一個新的對象了。
Android基礎入門教程——10.11 傳感器專題(2)——方向傳感器標簽(空格分隔): Android基礎入門教程本節
不管是出於什麼樣地考慮,android系統終究是提供了hardware層來封裝了對Linux的驅動的訪問,同時為上層提供了一個統一的硬件接口和硬件形態。一.Hardwar
前言距離寫上一篇自定義View文章已經大半年過去了,一直想繼續寫,但是無奈技術有限,生怕誤人子弟。這段時間項目剛剛完成,有點時間,跟著大神的腳步,鞏固下自定義View的相
今天使用正則表達式匹配指定目錄下的所有媒體文件,下面將這份代碼簡化了,可以收藏下來,當作工具類。[java] package match; &nb