web-dev-qa-db-ja.com

RxJava(Android)内でonErrorを適切に処理する方法は?

デバイスにインストールされているアプリのリストを取得しています。費用のかかる操作なので、そのためにRxを使用しています。

    Observable<List> observable = Observable.create(subscriber -> {
        List result = getUserApps();

        subscriber.onNext(result);
        subscriber.onError(new Throwable());
        subscriber.onCompleted();
    });

    observable
            .map(s -> {
                ArrayList<String> list = new ArrayList<>();
                ArrayList<Application> applist = new ArrayList<>();
                for (Application p : (ArrayList<Application>) s) {
                    list.add(p.getAppName());
                    applist.add(p);
                }
                return applist;
            })
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnError(throwable -> L.e(TAG, "Throwable " + throwable.getMessage()))
            .subscribe(s -> createListView(s, view));

しかし、私の問題はエラーの処理にあります。通常、ユーザーはこの画面を起動し、アプリの読み込みを待ち、最適なものを選択して次のページに進みます。ただし、ユーザーがUIをすばやく変更すると、NullPointerでアプリがクラッシュします。

さて、私はこれを実装しましたonError。しかし、それでもまだ機能せず、上記のユースケースでは次のようにスローされます。

    04-15 18:12:42.530  22388-22388/pl.digitalvirgo.safemob E/AndroidRuntime﹕ FATAL EXCEPTION: main
        Java.lang.IllegalStateException: Exception thrown on Scheduler.Worker thread. Add `onError` handling.
                at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.Java:52)
                at Android.os.Handler.handleCallback(Handler.Java:730)
                at Android.os.Handler.dispatchMessage(Handler.Java:92)
                at Android.os.Looper.loop(Looper.Java:176)
                at Android.app.ActivityThread.main(ActivityThread.Java:5419)
                at Java.lang.reflect.Method.invokeNative(Native Method)
                at Java.lang.reflect.Method.invoke(Method.Java:525)
                at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:1046)
                at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:862)
                at dalvik.system.NativeStart.main(Native Method)
         Caused by: rx.exceptions.OnErrorNotImplementedException
                at rx.Observable$31.onError(Observable.Java:7134)
                at rx.observers.SafeSubscriber._onError(SafeSubscriber.Java:154)
                at rx.observers.SafeSubscriber.onError(SafeSubscriber.Java:111)
                at rx.internal.operators.OperatorDoOnEach$1.onError(OperatorDoOnEach.Java:70)
                at rx.internal.operators.NotificationLite.accept(NotificationLite.Java:147)
                at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.pollQueue(OperatorObserveOn.Java:177)
                at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.access$000(OperatorObserveOn.Java:65)
                at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber$2.call(OperatorObserveOn.Java:153)
                at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.Java:47)
                at Android.os.Handler.handleCallback(Handler.Java:730)
                at Android.os.Handler.dispatchMessage(Handler.Java:92)
                at Android.os.Looper.loop(Looper.Java:176)
                at Android.app.ActivityThread.main(ActivityThread.Java:5419)
                at Java.lang.reflect.Method.invokeNative(Native Method)
                at Java.lang.reflect.Method.invoke(Method.Java:525)
                at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:1046)
                at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:862)
                at dalvik.system.NativeStart.main(Native Method)
         Caused by: Java.lang.Throwable
                at pl.digitalvirgo.safemob.fragments.wizard.ApplicationsFragment.lambda$getAppList$25(ApplicationsFragment.Java:267)
                at pl.digitalvirgo.safemob.fragments.wizard.ApplicationsFragment.access$lambda$2(ApplicationsFragment.Java)
                at pl.digitalvirgo.safemob.fragments.wizard.ApplicationsFragment$$Lambda$3.call(Unknown Source)
                at rx.Observable$1.call(Observable.Java:145)
                at rx.Observable$1.call(Observable.Java:137)
                at rx.Observable.unsafeSubscribe(Observable.Java:7304)
                at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.Java:62)
                at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.Java:47)
                at Java.util.concurrent.Executors$RunnableAdapter.call(Executors.Java:390)
                at Java.util.concurrent.FutureTask.run(FutureTask.Java:234)
                at Java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.Java:153)
                at Java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.Java:267)
                at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1080)
                at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:573)
                at Java.lang.Thread.run(Thread.Java:841)

この問題を適切に処理するにはどうすればよいですか?

25
a_dzik

.doOnError()は演算子であり、Subscriberの一部ではありません。

したがって、.doOnError()を持つことは、実装されたonError()としてカウントされません。

コメントの1つにある質問については、もちろんラムダを使用できます。

この場合、単に置き換える

.doOnError(throwable -> L.e(TAG, "Throwable " + throwable.getMessage()))
.subscribe(s -> createListView(s, view))

.subscribe(s -> createListView(s, view),
    throwable -> L.e(TAG, "Throwable " + throwable.getMessage()))
19
LukeJanyga

私の見解は次のとおりです。あなたはおそらくAction1を

_.subscribe(s -> createListView(s, view));
_

抽象メソッドonErrorを持つSubscriberまたはObserverに置き換える必要があります。このメソッドはsubscriber.onError(new Throwable());から呼び出されます

_EDIT:_これは私がやる方法です。よく見ると、あなたのコードの主な問題は、エラーがなくても_subscriber.onError_を呼び出す初期の部分だと思います。技術的にデータを操作せずにそのまま渡すため、mapもおそらく必要ありません。しかし、後で必要になる場合に備えて、それを残しました。

_     Observable.create(new Observable.OnSubscribe<Application>() {
        @Override
        public void call(Subscriber<? super Application> subscriber) {
            List result = getUserApps();
            if (result != null){
                for (Application app : result){
                     subscriber.onNext(app);
                }
                subscriber.onComplete();
            }else{
                subscriber.onError(new IOException("no permission / no internet / etc"));
               //or if this is a try catch event you can pass the exception
            }     
        }
     })
    .subscribeOn(Schedulers.io())//the thread *observer* runs in
    .observeOn(AndroidSchedulers.mainThread())//the thread *subscriber* runs in
    .map(new Func1<Application, String>() {

        // Mapping methods are where data are manipulated. 
        // You can simply skip this and 
        //do the same thing in Subscriber implementation
        @Override
        public String call(Application application) {
            return application.getName();
        }
    }).subscribe(new Subscriber<String>() {
        @Override
        public void onCompleted() {
           Toast.makeText(context, "completed", Toast.LENGTH_SHORT).show();
           //because subscriber runs in main UI thread it's ok to do UI stuff
           //raise Toast, play sound, etc
        }

        @Override
        public void onError(Throwable e) {
           Log.e("getAppsError", e.getMessage());
           //raise Toast, play sound, etc
        }

        @Override
        public void onNext(String s) {
            listAdapter.add(s);
        }
    });
_
12
inmyth

初心者の応答は次のとおりです(私はjavarxが初めてで、最終的にこの問題を修正したため)。

実装は次のとおりです。

    Observable.create(new Observable.OnSubscribe<RegionItem>() {
                @Override
                public void call(Subscriber<? super RegionItem> subscriber) {
                    subscriber.onError(new Exception("TADA !"));
                }
            })
            .doOnNext(actionNext)
            .doOnError(actionError)
            .doOnCompleted(actionCompleted)
            .subscribe();

この以前の実装では、サブスクライブするとエラーフローがトリガーされ、アプリケーションがクラッシュします。

問題は、subscribe()呼び出しからのエラーを管理する必要があることです。 「doOnError(...)」は、エラーのクローンを作成し、エラー後に何らかのアクションを実行する新しい場所を提供する一種のヘルパーです。ただし、エラーは処理しません。

だからあなたはそれであなたのコードを変更する必要があります:

    Observable.create(new Observable.OnSubscribe<RegionItem>() {
                @Override
                public void call(Subscriber<? super RegionItem> subscriber) {
                    subscriber.onError(new Exception("TADA !"));
                }
            })
            .subscribe(actionNext, actionError, actionCompleted);

実際の説明についてはわかりませんが、これは私がそれを修正する方法です。それが役立つことを願っています。

6
Tobliug