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!!) } } }
问题原因
- OkHttp拦截器运行在自身的Dispatcher线程池内,当拦截器抛出未捕获异常时,OkHttp会将其包装为
java.lang.Error抛出,而代码中的try-catch仅捕获Exception,无法捕获Error(Error是Throwable的子类,不属于Exception体系)。 - 若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
相关产品推荐
相关产品推荐

