You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Retrofit拦截器抛出的异常无法被Catch块捕获

Retrofit拦截器抛出的NoConnectivityException无法被捕获导致崩溃

问题现象

使用Retrofit拦截器检测网络状态,无网络时抛出NoConnectivityException,但Coroutine中的try-catch块无法捕获该异常,最终导致应用崩溃,日志显示异常被包装为java.lang.Error抛出。

崩溃日志

E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: com.msm.ProjectApp, PID: 9571
**java.lang.Error: com.msm.ProjectApp.Network.NoConnectivityException: No Internet Connection**
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1173)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at java.lang.Thread.run(Thread.java:923)
 Caused by: com.msm.ProjectApp.Network.NoConnectivityException: No Internet Connection
    at com.msm.ProjectApp.Network.AuthInterceptor.intercept(AuthInterceptor.kt:18)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:212)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
    at okhttp3.RealCall$AsyncCall.execute(RealCall.java:172)
    at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
    at java.lang.Thread.run(Thread.java:923) 

自定义异常类

class NoConnectivityException : IOException() {
    override val message: String
        get() = "No Internet Connection"
}

Auth拦截器代码

class AuthInterceptor(var authToken: String) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {

        if (!isNetworkAvailable(AppClass.context)) {
            Log.d("InterceptorTag", "intercept: no internet")
            throw NoConnectivityException()
        }

        Log.d("InterceptorTag", "intercept: yes internet")

        val newRequest = chain.request().newBuilder() //获取新请求
        newRequest.addHeader("Authorization", authToken)

        return chain.proceed(newRequest.build())
    }

    private fun isNetworkAvailable(context: Context): Boolean {
        val connectivityManager =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val nw = connectivityManager.activeNetwork ?: return false
            val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
            return when {
                actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
                actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
                //支持以太网连接的设备
                actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
                //检查蓝牙网络连接
                actNw.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
                else -> false
            }
        } else {
            return connectivityManager.activeNetworkInfo?.isConnected ?: false
        }
    }
}

Retrofit调用代码

CoroutineScope(Dispatchers.IO).launch {
    try {
        val categoriesResponse = sharedViewmodel.memeApi.getCategories()
        adapterCategories.setCategories(categoriesResponse.body()!!.data.categories)
        withContext(Dispatchers.Main) {
            adapterCategories.notifyDataSetChanged()
        }
    } catch (e: Exception) { //我也尝试过捕获NoConectivityException
        withContext(Dispatchers.Main) {
            showToast(e.message!!)
        }
    }
}

问题原因

  1. OkHttp拦截器运行在自身的Dispatcher线程池内,当拦截器抛出未捕获异常时,OkHttp会将其包装为java.lang.Error抛出,而代码中的try-catch仅捕获Exception,无法捕获Error(Error是Throwable的子类,不属于Exception体系)。
  2. 若Retrofit未正确配置Coroutine CallAdapter,异常无法被Coroutine上下文正确传递,导致局部try-catch无法捕获。

解决方案

方案一:拦截器返回错误Response而非抛出异常(推荐)

修改拦截器,构造自定义错误Response返回,避免抛出异常:

override fun intercept(chain: Interceptor.Chain): Response {
    if (!isNetworkAvailable(AppClass.context)) {
        Log.d("InterceptorTag", "intercept: no internet")
        // 返回自定义错误Response
        return Response.Builder()
            .request(chain.request())
            .protocol(Protocol.HTTP_1_1)
            .code(408) // 使用请求超时码或自定义错误码(如499)
            .message("No Internet Connection")
            .body(ResponseBody.create(MediaType.parse("text/plain"), ""))
            .build()
    }

    Log.d("InterceptorTag", "intercept: yes internet")
    val newRequest = chain.request().newBuilder()
        .addHeader("Authorization", authToken)
        .build()

    return chain.proceed(newRequest)
}

调用处检查Response状态处理错误:

CoroutineScope(Dispatchers.IO).launch {
    try {
        val categoriesResponse = sharedViewmodel.memeApi.getCategories()
        if (categoriesResponse.isSuccessful) {
            categoriesResponse.body()?.let { response ->
                adapterCategories.setCategories(response.data.categories)
                withContext(Dispatchers.Main) {
                    adapterCategories.notifyDataSetChanged()
                }
            }
        } else {
            val errorMsg = if (categoriesResponse.code() == 408) {
                "No Internet Connection"
            } else {
                categoriesResponse.message() ?: "Unknown Error"
            }
            withContext(Dispatchers.Main) {
                showToast(errorMsg)
            }
        }
    } catch (e: Exception) {
        withContext(Dispatchers.Main) {
            showToast(e.message ?: "Request Failed")
        }
    }
}

方案二:捕获Throwable而非Exception

若坚持在拦截器中抛出异常,修改catch块捕获Throwable(不推荐,会捕获所有错误包括系统级错误):

CoroutineScope(Dispatchers.IO).launch {
    try {
        val categoriesResponse = sharedViewmodel.memeApi.getCategories()
        adapterCategories.setCategories(categoriesResponse.body()!!.data.categories)
        withContext(Dispatchers.Main) {
            adapterCategories.notifyDataSetChanged()
        }
    } catch (e: Throwable) {
        val errorMsg = if (e is NoConnectivityException) {
            "No Internet Connection"
        } else {
            e.message ?: "Unknown Error"
        }
        withContext(Dispatchers.Main) {
            showToast(errorMsg)
        }
    }
}

方案三:确保Retrofit配置Coroutine CallAdapter

检查Retrofit构建代码,添加Coroutine CallAdapter,确保挂起函数的异常能被Coroutine上下文捕获:

val retrofit = Retrofit.Builder()
    .baseUrl("YOUR_BASE_URL")
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(CoroutineCallAdapterFactory()) // 适配Coroutine
    .client(okHttpClient)
    .build()

同时确保接口定义为挂起函数:

interface MemeApi {
    @GET("categories")
    suspend fun getCategories(): Response<YourCategoriesResponseModel>
}

内容的提问来源于stack exchange,提问作者CodingDevil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 00:33:08