RxLifecycleでのライフサイクルへのバインドについて

やぎすけAdventCalendar2016 Android

Posted on Dec 12


こんにちは、やぎにいです。
やぎすけ Advent Calendar 2016の12日目です。

昨日は僕がRxJava+RxAndroid+RxLifecycleを使った非同期処理を行うを書きました。
どうやら今日の横浜の天気は晴のち曇らしいです。

今日は昨日も使ったRxLifecycleのバインド挙動についてお話したいと思います。

はじめに

RxLifecycleとは昨日の記事で紹介したとおりRxJavaのObserverをAndroidのライフサイクルにバインドすることによってメモリリークやエラーを回避するためのものです(かなりざっくり)
RxJavacompose()メソッドで呼び出される場所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しないのです。
onCompletedonErrorで最終的にunsubscribeされるので厳密にさっきの文言は間違ってはいないのですが、頭においてほしいのはunsubscribe()の挙動を期待しないということです。

よくある勘違いとしてRxLifecycleでバインドしてあげればActivityFragmentが破棄されたら即時に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()では、保存された値を使ってActivityFragmentを呼び出す処理をしていたとすると、ユーザーはアプリを終了したのになんかまた勝手に立ち上がったぞ的な挙動を起こすことがあります。


こうしよう

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()SingleObservableが処理開始する(onPause()に明示的バインド)
  • onResume()Completableが処理を開始する(onResume()に対応したonPause()にバインド)
  • 5秒以内にホームボタンを押すなりしてonPause()を呼ぶとすべての処理が終了されてそれぞれonError(CancellationException)onCompleted()が呼ばれる

のを確認できると思います。
是非試してみてください。

余談ではありますが、bindToLifecycle()はそれぞれObservableSingleCompletableで以下のようにしてあげる必要があります

  • Observable -> .compose(bindToLifecycle())
  • Single -> .compose(bindToLifecycle.forSingle())
  • Completable -> .compose(bindToLifecycle.forCompletable())



おわりに

今日はRxLifecycleでよく見られる勘違いについて書きました。
適切に処理してあげないと意図しない挙動を起こす原因となるので気をつけましょう(過去の自分へ)

以上、やぎにいでした!


このエントリーをはてなブックマークに追加
comments powered by Disqus

<< dropbox-java-sdkをRxJavaで使うライブラリを作ってみました     RxJava+RxAndroid+RxLifecycleを使った非同期処理を行う >>



2018やぎ小屋