web-dev-qa-db-ja.com

RXjava2でObservableEmitterのonErrorを呼び出しているときにUndeliverableExceptionが発生しました

以下のようなエミッターを作成するメソッドがありますが、retrofitコールバックでonErrorを呼び出すことに問題があります(おそらくそれは正常な動作です)。 onErrorを呼び出そうとすると、UndeliverableExceptionが発生しました。

これを解決するには、subscriber.isDiposed()をチェックして、UIレベルを通知する必要があるonErrorcozをどのように呼び出すことができるか疑問に思います。

  • 追加1
--> RxJava2CallAdapterFactoryalready implemented   
private static Retrofit.Builder builderSwift = new Retrofit.Builder()
           .baseUrl(URL_Swift)
           .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
           .addConverterFactory(GsonConverterFactory.create())
           .addConverterFactory(new ToStringConverterFactory());

--> When i added below code to application class app won't crash
--> but i get Java.lang.exception instead of my custom exception  

RxJavaPlugins.setErrorHandler(Functions<Throwable>emptyConsumer());

@Override
    public void onFileUploadError(Throwable e) {
        Log.d(TAG, "onFileUploadError: " + e.getMessage());
    }
public Observable<UploadResponseBean> upload(final UploadRequestBean uploadRequestBean, final File file) {
    return Observable.create(new ObservableOnSubscribe<UploadResponseBean>() {
        @Override
        public void subscribe(@NonNull final ObservableEmitter<UploadResponseBean> subscriber) throws Exception {

                 // ---> There are no problem with subscriber while calling onError        

                // ---> Retrofit2 service request 
                ftsService.upload(token, uploadRequestBean, body).enqueue(new Callback<UploadResponseBean>() {
                    @Override
                    public void onResponse(Call<UploadResponseBean> call, Response<UploadResponseBean> response) {

                        if (response.code() == 200){
                            // --->  calling onNext works properly
                            subscriber.onNext(new UploadResponseBean(response.body().getUrl()));
                        }
                        else{
                            // --->  calling onError throws UndeliverableException
                            subscriber.onError(new NetworkConnectionException(response.message()));                
                        }
                    }

                    @Override
                    public void onFailure(Call call, Throwable t) {
                        subscriber.onError(new NetworkConnectionException(t.getMessage()));
                    }
                });
        }
    });
}
9
Talha

バージョン2.1.1以降tryOnErrorが利用可能です:

エミッターAPI(FlowableEmitter、SingleEmitterなど)は、シーケンスがキャンセル/破棄されない場合にThrowableを発行しようとする新しいメソッドtryOnErrorを備えています。通常のonErrorとは異なり、ダウンストリームがイベントを受け入れる意思がなくなった場合、メソッドはfalseを返し、UndeliverableExceptionを通知しません。

https://github.com/ReactiveX/RxJava/blob/2.x/CHANGES.md

10
Yazazzello

問題は、Subscriberがすでに破棄されているかどうかを確認する必要があると言っているようなものです。これは、Subscriberが既に破棄された後にスローされるエラーに関してRxJava2がより厳密であるためです。
RxJava2は、この種のエラーを_RxJavaPlugins.onError_に配信します。これは、デフォルトでスタックトレースに出力し、スレッドに捕捉されない例外ハンドラーを呼び出します。あなたは完全な説明を読むことができます ここ

ここで何が起こっているのかというと、クエリが実行されてエラーが配信される前に、おそらくこのObservableのサブスクライブを解除(破棄)しているため、UndeliverableExceptionを取得します。

UIレベルを通知する必要があるonErrorcozをどのように呼び出すことができるのだろうか。

これは、UIのサブスクライブが解除された後に発生するため、UIは気にしないはずです。通常のフローでは、このエラーは適切に配信されます。

実装に関するいくつかの一般的なポイント:

  • 以前に登録を解除したことがある場合は、onErrorでも同じ問題が発生します。
  • ここにはキャンセルロジックがないため(これがこの問題の原因です)、Subscriberがサブスクライブ解除されている場合でもリクエストは続行されます。
  • このロジックを実装する場合でも(ObservableEmitter.setCancellable()/setDisposable()を使用)、リクエストが完了する前にサブスクライブを解除すると、この問題が発生します。これにより、キャンセルとonFailureロジックが発生します。 onError()を呼び出し、同じ問題が発生します。
  • retrofitを介して非同期呼び出しを実行すると、指定されたサブスクリプションSchedulerは、実際のリクエストをSchedulerスレッドではなく、サブスクリプションのみで発生させます。 _Observable.fromCallable_を使用して、Retrofitブロッキング呼び出しexecuteを使用して、実際のスレッド呼び出しが発生するかどうかをより詳細に制御できます。

要約すると、この場合、onError()への呼び出しをObservableEmitter.isDiposed()で保護することをお勧めします。
しかし、ベストプラクティスは、Retrofit RxJava呼び出しアダプターを使用することだと思います。そのため、Retrofit呼び出しを実行することでObservableがラップされ、すでにこれらすべての考慮事項があります。

8
yosriz

この問題は、フラグメントでビューモデルを取得するときに誤ったコンテキストを使用したことが原因であることがわかりました。

ViewModelProviders.of(requireActivity(), myViewModelFactory).get(MyViewModel.class);

このため、ビューモデルはフラグメントではなくアクティビティのコンテキストで存在していました。次のコードに変更すると、問題が修正されました。

ViewModelProviders.of(this, myViewModelFactory).get(MyViewModel.class);
0
arenaq