有时 RxJava onError 不会捕获异常

Posted

技术标签:

【中文标题】有时 RxJava onError 不会捕获异常【英文标题】:Sometimes RxJava onError doesn’t catch Exception 【发布时间】:2020-08-15 23:17:53 【问题描述】:

我有新闻列表。当用户单击一条新闻时,我会显示完整的新闻信息。在那一刻,我在标题中使用此消息的别名发送到后端请求。有时我的应用程序崩溃。但是这个请求有 onError 处理程序。我无法重现此错误。但我在 Crashlytics 上看到了这个错误。

我知道有非 ASCII 符号。无法显示所有标题。我有很多新闻,其中一个可以有非 ASCII 符号。我需要确保这个错误会在 onError 中得到处理,并且 App 不会崩溃。

这是日志错误

Fatal Exception: java.lang.IllegalArgumentException: Unexpected char 0x430 at 12 in Params value: "itemId":"акция"
       at okhttp3.Headers$Companion.checkValue(Headers.java:434)
       at okhttp3.Headers$Companion.access$checkValue(Headers.java:346)
       at okhttp3.Headers$Builder.add(Headers.java:245)
       at okhttp3.Request$Builder.addHeader(Request.java:210)
       at com.example.NetworkModule.lambda$provideAuthOkHttpClient$1(NetworkModule.java:112)
       at com.example.-$$Lambda$NetworkModule$0kozbr2vzEMUs4EGFjGRxf09B6g.intercept(-.java)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:112)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:87)
       at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:219)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:112)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:87)
       at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:194)
       at okhttp3.RealCall$AsyncCall.run(RealCall.java:138)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
       at java.lang.Thread.run(Thread.java:762)

这里是示例代码。

mNetworkHelper.sendGrafanaMetrics()
                    .compose(RxUtil.applySchedulers())
                    .subscribe(response -> 
                    , this::silentError);

这是 NetworkModule 类的一部分。 112行是.addHeader(PARAMS_HEADER, params);

@Module
public class NetworkModule implements Constants 
    @Provides
    @Singleton
    @Named("authClient")
    OkHttpClient provideAuthOkHttpClient(Context context, PreferencesHelper preferencesHelper, VeonApi veonApi, UserAgent userAgent, SSLSocketFactory sslSocketFactory, TrustManager[] trustManager) 
        Cache cache = new Cache(context.getCacheDir(), cacheSize);

        HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(message -> Log.d("myLogger", message))
                .setLevel(HttpLoggingInterceptor.Level.BODY);


        Interceptor authInterceptor = chain -> 
            Request original = chain.request();
            String params = userAgent.getJsonString();
            Request.Builder requestBuilder = original.newBuilder()
                    .addHeader(PARAMS_HEADER, params);
            String ticket = preferencesHelper.getTicket();

            if (addTicket && ticket != null) 
                Timber.v("--===adding ticket: %s", ticket);
                requestBuilder.addHeader(AUTHORIZATION_HEADER, ticket);
             else 
                Timber.v("not adding ticket %s", ticket);
            
            okhttp3.Response response = chain.proceed(requestBuilder.build());
            if (response.code() == 401) 
                Timber.v("Ticket Expired");
                if (preferencesHelper.rememberMe()) 
                    refreshTicket(veonApi, preferencesHelper);
                    ticket = preferencesHelper.getTicket();
                    if (ticket != null) 
                        requestBuilder.addHeader(AUTHORIZATION_HEADER, ticket);
                    
                    response = chain.proceed(requestBuilder.build());
                 else 
                    return new okhttp3.Response.Builder()
                            .code(418)
                            .request(chain.request())
                            .protocol(response.protocol())
                            .message(context.getString(R.string.ticket_reload_required))
                            .body(ResponseBody.create(null,
                                    new Gson().toJson(new ResponseException(418, context.getString(R.string.ticket_reload_required)))))
                            .build();
                
            
            return response;
        ;

        return new OkHttpClient.Builder()
                .protocols(Collections.singletonList(Protocol.HTTP_1_1))
                .retryOnConnectionFailure(true)
                .addInterceptor(logInterceptor)
                .addInterceptor(authInterceptor)
                .cache(cache)
                .connectTimeout(TIMEOUT_SEC, TimeUnit.SECONDS)
                .writeTimeout(TIMEOUT_SEC, TimeUnit.SECONDS)
                .readTimeout(TIMEOUT_SEC, TimeUnit.SECONDS)
                .hostnameVerifier((hostname, session) -> true)//todo should not be in prod
                .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManager[0])//todo should not be in prod
                .build();
    

sendGrafanaMetrics 网络请求只有标头数据。此请求需要用于统计目的。这是标头请求的一部分。 "itemId":"новые данные"。有时在 itemId 中我可以收到非 ASCII 符号

【问题讨论】:

NetworkModule.java:112 发生了什么?您能否提供一些有关此行的代码。 sendGrafanaMetrics 的外观如何?我的假设是,在被包装成 Observable(例如反应型)之前,一些调用急切地完成并失败,因此没有 onError。示例:Observable.just(myMethod()); void myMethod() throw new RunTimeException(); 。在此示例中,您不会收到 onError,因为 myMethod 在 Observable#just 之前被调用。通过调用 myMethod 一个异常被抛出并传播到被调用的(当前线程)。 我更新了问题信息。但我自己无法重现此错误。我在 Crashlytics 中看到了这个错误。我也不能像你说的那样重现这个错误。我试着像这样重现这个错误。在加载新闻之前,我关闭了互联网并捕获了 UnknownHostException,然后我尝试使用带有非 ASCII 符号的标头发送请求。 您确定异常会导致应用崩溃吗?很明显,堆栈跟踪来自线程池中的线程。当线程池中的线程崩溃时,不应停止应用程序,因为 UI-Eventloop 应该仍在运行。堆栈跟踪显示,正在执行 RealCall,并且响应 (userAgent.getJsonString()) 包含无效字符。因此,当 userAgent.getJsonString 包含非 ASCII 字符时,它应该是可重现的。你在任何地方都有像 OkHttpClient#newCall(request).execute() 这样的电话吗?这会将作业排入线程池中。 是的,我敢肯定,异常会导致应用程序崩溃,因为我在 crashlytics 中看到了这种崩溃。我有大约 40000 个用户。大约 6 个用户收到此错误 【参考方案1】:

标题不能包含非 ASCII 字符。请参考https://github.com/square/okhttp/issues/2896。而不是您应该尝试将标头值解码为 base64。

【讨论】:

以上是关于有时 RxJava onError 不会捕获异常的主要内容,如果未能解决你的问题,请参考以下文章

RxJava之错误处理

在 200 网络响应上调用 onError 而不是 onNext - RxJava,Retrofit

RxJava 错误处理

Java中有多个异常, 如何确定捕获顺序(多个catch),先从上到下执行,判断异常的大小,如果包含捕到异常,就进入这个catch,后面的就不再执行

单元测试 RxJava onComplete、onNext 和 onError

对前端异常window error捕获的全面总结