こんにちは、やぎにいです。
やぎすけ Advent Calendar 2016の12日目です。
昨日は僕がRxJava+RxAndroid+RxLifecycleを使った非同期処理を行うを書きました。
どうやら今日の横浜の天気は晴のち曇らしいです。
今日は昨日も使ったRxLifecycleのバインド挙動についてお話したいと思います。
RxLifecycleとは昨日の記事で紹介したとおりRxJavaのObserverをAndroidのライフサイクルにバインドすることによってメモリリークやエラーを回避するためのものです(かなりざっくり)
RxJavaのcompose()メソッドで呼び出される場所onCreate()やonResume()に対応したライフサイクルで破棄されるbindToLifecycle()、任意のイベントにバインドするbindUntilEvent(EVENT)があります。
基本的な使い方はそんな感じです。
trelloRxLifecycle
RxLifecycleでの挙動を調べるとQiita等の記事ではよく
RxLifecycleではライフサイクルにバインドし所定のイベントで
unsubscribe()される
という記載をよく見ます。(僕も困ったときにたどり着いた記事は大体これが書いてあった)
これは間違い(言い過ぎではあるけど)で、RxLifecycleのREADMEにも以下が書いてあります。
RxLifecycle does not actually unsubscribe the sequence. Instead it terminates the sequence. The way in which it does so varies based on the type:
・Observable - emits onCompleted()
・Single and Completable - emits onError(CancellationException)If a sequence requires the Subscription.unsubscribe() behavior, then it is suggested that you manually handle the Subscription yourself and call unsubscribe() when appropriate
ざっくり訳すと
unsubscribeはしない。その代わりにObservableではonCompleted()、Single, CompletableではonError(CancellationException)を呼ぶので、unsubscribe()の動作が必要なら必要に応じて手動で呼び出すのをおすすめするぞ。
とあります。
RxLifecycleはunsubscribeしないのです。
onCompletedやonErrorで最終的にunsubscribeされるので厳密にさっきの文言は間違ってはいないのですが、頭においてほしいのはunsubscribe()の挙動を期待しないということです。
よくある勘違いとしてRxLifecycleでバインドしてあげればActivityやFragmentが破棄されたら即時にObservableの処理がそこで破棄される。という勘違いです。
それを勘違いしているとちょっと困ることが起こったりします。
以下のObservableの処理があったとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | hogeObservable .compose(bindToLifecycle()) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<Hoge> { @Override public coid onCompleted() { // onNext()で保存した値を呼び出して何かしらの処理をする } @Override public onError(Throwable e) { } @Override public onNext(Hoge hoge) { // ここでPreferenceに値を書き込む等の処理をする } }); |
勘違いの前提として、ちゃんとbindToLifecycle()を行ったので、Activityが破棄されるときはこのObservableもunsubscibe()で破棄されるからその時点でonNext()onError()onCompleted()のどれも呼ばれなくなるという勘違いをしているとします。
これが危ないのはonCompleted()での処理です。
例えばユーザーがこのアプリを開いてアプリがAPIを叩いたが、通信が長引いて全部データを保存することが出来ずにホーム画面に戻ったとします。
するとRxLifecycleの作用でonCompleted()が呼ばれます。このコードではonCompleted()の中ではonNext()でAPIから返ってきた値を保存した物を呼び出して使おうとしています。
ですが、APIの通信が完了しておらず、値の保存が出来ていないので、値の呼び出しに失敗します。最悪アプリは閉じてるけどNullPointerExceptionが発生する可能性があります。
非常に良くないですね。
もし、onCompleted()では、保存された値を使ってActivityやFragmentを呼び出す処理をしていたとすると、ユーザーはアプリを終了したのになんかまた勝手に立ち上がったぞ的な挙動を起こすことがあります。
RxLifecycleはとにかくonCompleted()とonError(CancellationException)を呼ぶ(しつこい)のでそれを前提にした設計にしよう。ということです。
じゃあunsubscribe()される前提の処理はどう書けばいいんだよ!みたいになると思いますが、その場合はdoOnUnsubscribe()に書いてください。unsubscibe()されたらそれが呼ばれます。
こんな検証コードを書いてみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | public class MainActivity extends RxAppCompatActivity { private static final String TAG = "RxLifecycleAndroid"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate()"); setContentView(R.layout.activity_main); // for Observable // bind to onPause() Observable.interval(5, TimeUnit.SECONDS) .doOnSubscribe(new Action0() { @Override public void call() { Log.i(TAG, "[Observables] : Started in onCreate(), running until onPause()"); } }) .doOnUnsubscribe(new Action0() { @Override public void call() { Log.i(TAG, "[Observables] : Unsubscribing subscription from onCreate()"); } }) .compose(this.<Long>bindUntilEvent(ActivityEvent.PAUSE)) .subscribe(new Observer<Long>() { @Override public void onCompleted() { Log.i(TAG, "[Observables] : onCompleted()"); } @Override public void onError(Throwable e) { Log.i(TAG, "[Observables] : onError()"); } @Override public void onNext(Long aLong) { Log.i(TAG, "[Obsercables] : onNext()"); } }); // for Single // if onPause() is executed within 5 seconds, onError (CancellationException) is called. Observable.interval(5, TimeUnit.SECONDS) .doOnSubscribe(new Action0() { @Override public void call() { Log.i(TAG, "[Single] : Started in onCreate(), running until onPause()"); } }) .doOnUnsubscribe(new Action0() { @Override public void call() { Log.i(TAG, "[Single] : Unsubscribing subscription from onCreate()"); } }) .toSingle() .compose(this.bindUntilEvent(ActivityEvent.PAUSE).<Long>forSingle()) .subscribe(new SingleSubscriber<Long>() { @Override public void onSuccess(Long value) { Log.i(TAG, "[Single] : onSuccess()"); } @Override public void onError(Throwable error) { if (error instanceof CancellationException) { Log.i(TAG, "[Single] : onError(CancellationException)"); } } }); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume()"); // for Completable // bindToLifecycle -> onPause(); Observable.interval(5, TimeUnit.SECONDS) .doOnSubscribe(new Action0() { @Override public void call() { Log.i(TAG, "[Completable] : Started in onCreate(), running until onResume()"); } }) .doOnUnsubscribe(new Action0() { @Override public void call() { Log.i(TAG, "[Completable] : Unsubscribing subscription from onResume()"); } }) .toCompletable() .compose(this.bindToLifecycle().<Long>forCompletable()) .subscribe(new CompletableSubscriber() { @Override public void onCompleted() { Log.i(TAG, "[Completable] : onCompleted()"); } @Override public void onError(Throwable e) { if (e instanceof CancellationException) { Log.i(TAG, "[Completable] : onError(CancellationException)"); } } @Override public void onSubscribe(Subscription d) { Log.i(TAG, "[Completable] : onSubscribe()"); } }); } @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause()"); } } |
ちょっと長いですが
onCreate()でSingleとObservableが処理開始する(onPause()に明示的バインド)onResume()でCompletableが処理を開始する(onResume()に対応したonPause()にバインド)onPause()を呼ぶとすべての処理が終了されてそれぞれonError(CancellationException)とonCompleted()が呼ばれるのを確認できると思います。
是非試してみてください。
余談ではありますが、bindToLifecycle()はそれぞれObservableSingleCompletableで以下のようにしてあげる必要があります
.compose(bindToLifecycle()).compose(bindToLifecycle.forSingle()).compose(bindToLifecycle.forCompletable())今日はRxLifecycleでよく見られる勘違いについて書きました。
適切に処理してあげないと意図しない挙動を起こす原因となるので気をつけましょう(過去の自分へ)
以上、やぎにいでした!
<< dropbox-java-sdkをRxJavaで使うライブラリを作ってみました RxJava+RxAndroid+RxLifecycleを使った非同期処理を行う >>
2018やぎ小屋