处理由Kotlin Coroutines中的自定义okhttp拦截器抛出的异常

42 人关注

我在Android应用中使用了一个自定义的Interceptor和Retrofit客户端,在某些特定情况下会抛出一个异常。我正试图用Kotlin的程序来使它工作。

问题是,我无法处理之前提到的错误,因为当异常从拦截器实例中抛出时,它就会使整个应用程序崩溃,而不是在coroutine的try/catch语句中被捕获。当我使用Rx实现时,异常被完美地传播到onError回调中,在那里我能够以我需要的方式处理它。

我猜这与用于网络调用的底层线程有关,请看下面的日志,从调用的地方,从抛出异常之前的拦截器,以及堆栈跟踪。

2019-11-04 17:17:34.515 29549-29729/com.app W/TAG: Running thread: DefaultDispatcher-worker-1
2019-11-04 17:17:45.911 29549-29834/com.app W/TAG: Interceptor thread: OkHttp https://some.endpoint.com/...

2019-11-04 17:17:45.917 29549-29834/com.app E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
    Process: com.app, PID: 29549
    com.app.IllegalStateException: Passed refresh token can\'t be used for refreshing the token.
        at com.app.net.AuthInterceptor.intercept(AuthInterceptor.kt:33)

我应该怎么做才能从拦截器中正确捕捉和处理这个异常呢?我是不是遗漏了什么?

2 个评论
"我猜这与用于网络调用的底层线程有某种关系" -- 你是说在Rx的实现中,你的 "运行线程 "和 "拦截器线程 "都是一样的?你可能想编辑你的问题并提供一个最低限度的可重复的例子,显示拦截器代码和你未处理的自定义异常的完整堆栈跟踪。
只有IOException会被传播给调用者。所有其他的异常都被委托给未捕获的异常处理程序。这意味着你不应该抛出一个IllegalStateException,而应该抛出一个IOException
android
exception
okhttp
interceptor
kotlin-coroutines
mrpasqal
mrpasqal
发布于 2019-11-05
3 个回答
Jesse Wilson
Jesse Wilson
发布于 2019-11-05
已采纳
0 人赞同

你应该对IOException进行子类化,并使用它来将信息从你的拦截器发送到你的调用代码。

我们认为像IllegalStateException这样的其他异常是应用程序崩溃,并且不通过线程边界发送它们,因为我们不想让大多数调用者为捕捉它们而感到负担。

这似乎是这个问题的最干净的解决方案。我只是想知道为什么在Rx的实现中,任何异常都被转发给了调用者。是Retrofit的Rx适配器的内部问题吗?
我觉得这是一个非常糟糕的设计决定。为什么你认为你可以为用户决定他们是否要 "承担 "捕捉更多通用异常的负担?为什么我的自定义异常被强迫成为IOException,即使从语义上讲它们不是?
@fyodor这里有关于这个设计决定的讨论。github.com/square/okhttp/pull/5457
@JesseWilson 从什么时候开始,这让我浪费了半天时间!为什么?为什么?对我来说毫无意义。IOException = 网络问题。我使用这个异常来决定何时在网络重新上线时自动重试。我已经创建了一个关于它的问题github.com/square/retrofit/issues/3505
Amir Raza
Amir Raza
发布于 2019-11-05
0 人赞同

你可以在你的自定义Interceptor中捕获异常,并返回一个带有一些特定messagecode的空响应。我已经实现了一个自定义的Interceptor来处理这种情况,比如当你没有或者网络连接很慢的时候。实际上,coroutine的暂停功能在处理网络调用时,会产生异常。根据我的经验,你可以遵循两种方法。1.try...catch中包装你的所有网络呼叫,或2.创建一个自定义的Interceptor,在那里处理异常并返回一些特定的响应。

方法一。

try {
    webservice.login(username, password)
} catch (e: Exception) {
    //...
}

方法二。

创建一个自定义的Interceptor并在那里处理异常。

class LoggingInterceptor : Interceptor {

   @Throws(Exception::class)
   override fun intercept(chain: Interceptor.Chain): Response {
      val request = chain.request()
      try {
          val response = chain.proceed(request)
        
          val bodyString = response.body()!!.string()

          return response.newBuilder()
            .body(ResponseBody.create(response.body()?.contentType(), bodyString))
            .build()
      } catch (e: Exception) {
          e.printStackTrace()
          var msg = ""
          when (e) {
             is SocketTimeoutException -> {
                msg = "Timeout - Please check your internet connection"
             }
             is UnknownHostException -> {
                msg = "Unable to make a connection. Please check your internet"
             }
             is ConnectionShutdownException -> {
                msg = "Connection shutdown. Please check your internet"
             }
             is IOException -> {
                msg = "Server is unreachable, please try again later."
             }
             is IllegalStateException -> {
                msg = "${e.message}"
             }
             else -> {
                msg = "${e.message}"
             }
          }

          return Response.Builder()
                .request(request)
                .protocol(Protocol.HTTP_1_1)
                .code(999)
                .message(msg)
                .body(ResponseBody.create(null, "{${e}}")).build()
      }
   }
}

我已经创建了LoggingInterceptor的完整实现Gist,并打印了请求和响应的日志。LoggingInterceptor

Teo
谢谢你的Approach 2。它是如此有帮助!
哦,伙计,这是在整个互联网上最好的方法。
哦,伙计!你拯救了我的时间海洋,只是没有说你的解决方案方法2是像地狱一样工作。 非常感谢
Matheus Lima
Matheus Lima
发布于 2019-11-05
0 人赞同

我不知道你到底需要什么,但可以这样理解。

    OkHttpClient okHttpClient = new OkHttpClient.Builder()  
        .addInterceptor(new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                okhttp3.Response response = chain.proceed(request);

                // todo deal with the issues the way you need to
                if (response.code() == SomeCode) {
                   //do something
                    return response;
                }

                return response;
            }
        })
        .build();

Retrofit.Builder builder = new Retrofit.Builder()  
        .baseUrl(url)
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create());

Retrofit retrofit = builder.build();  

推荐产品

云服务器_QVM_云主机

云服务器_QVM_云主机

七牛云 QVM 以云服务器为核心,提供含云硬盘、云数据库、高防、负载均衡等解决方案为一体的云计算综合服务。
CDN_内容分发网络

CDN_内容分发网络

七牛 CDN 提供稳定快速的网络访问服务。保障您的音视频点播、大文件下载、应用及 Web 加速服务的稳定及连续性。
云存储_对象存储

云存储_对象存储

七牛云对象存储系统 kodo 支持中心和边缘存储,经过多年大规模用户验证已跻身先进技术行列,并广泛应用于海量数据管理的各类场景。
实时音视频通信_RTC

实时音视频通信_RTC

七牛实时音视频云为您提供一站式解决方案,零基础搭建音视频平台,快速支持视频通话、多人会议、互动直播、语音聊天室等多种业务场景。
页面原文内容由 mrpasqal、Jesse Wilson 等提供。七牛云提供翻译支持。
如您不希望该内容被发布至本网站,请发送邮件至:ug#qiniu.com(邮箱中 # 请改为 @)进行申诉,一经确认,我们将立即删除该内容。