techium

このブログは何かに追われないと頑張れない人たちが週一更新をノルマに技術情報を発信するブログです。もし何か調査して欲しい内容がありましたら、@kobashinG or @muchiki0226 までいただけますと気が向いたら調査するかもしれません。

RxJavaのmapのエラーハンドリングをする

RxJavaを使った場合によく活用されるであろうmapでエラーが発生した時の操作についてご説明します。

mapはObservableのストリームで渡される要素一つに対して実行する処理を記述するもので、その処理途中でエラーが発生した時のExceptionをハンドリングしてみましょう。

subscribeのonErrorを使う

map内で発生したExceptionはそのまま処理することはできません。
理由としてはRuntimeException以外のものをthrowされたとしても受け付けることができない構造になっています。
そのためIOExceptionやFileNotExceptionなどが発生したとしても自動的にExceptionをひろってsubscribeのonErrorには処理が移譲しません。
そこで例として下記のコードで見ていきましょう。
例は外部のHTTPサーバに対してHTTP GETを実行し、実行に失敗した時にonErrorへ処理を移譲します。

RxJavaのmapでのエラー処理(onError)

15行目から27行目まではokhttpを使ってHTTP GETを実行している処理になります。
そのリクエスト先は2行目のurlsの配列で渡しています。
この配列の3つ目のlocalhostが存在しないサーバとなり、実行するとサーバがないのでエラーになります。
エラーがが発生した時に28行目のcatchが働き、29行目のコードを実行します。 ここでthrowしているのはExceptionsのpropagateで生成されたRuntimeExceptionになります。
ここで引数で渡しているのはcatchした時に渡されてきたExceptionを渡すと、そのExceptionの内容をコピーしてRuntimeExceptionとしてスローし直します。

このコードを実行した結果は次の様になります。

D/techium: https://www.google.co.jp/?gws_rd=ssl
D/techium: onNext true
D/techium: http://localhost/
D/techium: onError
W/System.err: java.lang.RuntimeException: java.net.ConnectException: Failed to connect to localhost/127.0.0.1:80
W/System.err:     at rx.exceptions.Exceptions.propagate(Exceptions.java:55)
W/System.err:     at com.hatenablog.techium.rxjava.sample.MainActivity$2.call(MainActivity.java:59)
W/System.err:     at com.hatenablog.techium.rxjava.sample.MainActivity$2.call(MainActivity.java:39)
W/System.err:     at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:54)
W/System.err:     at rx.internal.operators.OperatorSubscribeOn$1$1.onNext(OperatorSubscribeOn.java:53)
W/System.err:     at rx.internal.operators.OnSubscribeFromArray$FromArrayProducer.fastPath(OnSubscribeFromArray.java:76)
W/System.err:     at rx.internal.operators.OnSubscribeFromArray$FromArrayProducer.request(OnSubscribeFromArray.java:58)
W/System.err:     at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
W/System.err:     at rx.Subscriber.setProducer(Subscriber.java:209)
W/System.err:     at rx.Subscriber.setProducer(Subscriber.java:205)
W/System.err:     at rx.Subscriber.setProducer(Subscriber.java:205)
W/System.err:     at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
W/System.err:     at rx.internal.operators.OnSubscribeFromArray.call(OnSubscribeFromArray.java:32)
W/System.err:     at rx.internal.operators.OnSubscribeFromArray.call(OnSubscribeFromArray.java:24)
W/System.err:     at rx.Observable.unsafeSubscribe(Observable.java:8741)
W/System.err:     at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
W/System.err:     at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
W/System.err:     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:237)
W/System.err:     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
W/System.err:     at java.lang.Thread.run(Thread.java:818)
W/System.err: Caused by: java.net.ConnectException: Failed to connect to localhost/127.0.0.1:80
W/System.err:     at okhttp3.internal.io.RealConnection.connectSocket(RealConnection.java:142)
W/System.err:     at okhttp3.internal.io.RealConnection.connect(RealConnection.java:111)
W/System.err:     at okhttp3.internal.http.StreamAllocation.findConnection(StreamAllocation.java:188)
W/System.err:     at okhttp3.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:127)
W/System.err:     at okhttp3.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
W/System.err:     at okhttp3.internal.http.HttpEngine.connect(HttpEngine.java:289)
W/System.err:     at okhttp3.internal.http.HttpEngine.sendRequest(HttpEngine.java:241)
W/System.err:     at okhttp3.RealCall.getResponse(RealCall.java:240)
W/System.err:     at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:198)
W/System.err:     at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:160)
W/System.err:     at okhttp3.RealCall.execute(RealCall.java:57)
W/System.err:     at com.hatenablog.techium.rxjava.sample.MainActivity$2.call(MainActivity.java:56)
W/System.err:   ... 21 more
W/System.err: Caused by: rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: http://localhost/
W/System.err:     at rx.exceptions.Exceptions.throwOrReport(Exceptions.java:189)
W/System.err:     at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:56)
W/System.err:   ... 19 more

このコードの特徴はエラーが発生したらその時点で処理が終わり、onErrorに処理が流れてそのまま終了します。
また、この方法で実行するとエラー内容の詳細を引き継いでいるので何が原因だったかも読み取ることができます。

他にもmapを継承して、Exceptionを使用できるように改造する手や、Observableのcreateを使ってExceptionを受け取ったりできるようにすることが可能ですが、この方法が一番簡単でかつ効果的だと思われます。

サンプルコード

github.com