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

RXJava如何统一处理缺失错误监听器/日志中缺失堆栈追踪问题

Retrofit统一错误处理器失效导致崩溃(无日志)的排查与解决

我最近在负责的项目里踩了个非常隐蔽的大坑——原本依赖的统一错误处理器完全没按预期工作,导致一堆难排查的问题。这次遇到的情况更头疼:某个非关键的Retrofit接口getSpots()返回404,我本来指望统一错误处理器只悄悄记录日志就行,结果程序直接崩溃,连错误日志都没打出来!后来试着给请求加了个空的onFailure回调,居然就正常运行了,这到底是怎么回事?

先贴一下我这边DynamicSyncCoordinatior里的相关代码片段:

class DynamicSyncCoordinatior(private val apiService: ApiService) {
    fun syncSpots() {
        apiService.getSpots()
            .enqueue(object : Callback<List<Spot>> {
                override fun onResponse(call: Call<List<Spot>>, response: Response<List<Spot>>) {
                    // 处理正常响应逻辑
                }
                // 原本这里没写onFailure,完全指望统一错误处理器兜底
            })
    }
}

问题根源分析

其实这是Retrofit回调机制里一个容易被忽略的细节:如果你在调用enqueue()时没有实现onFailure()回调,当请求发生错误(比如404、网络异常)时,Retrofit会直接抛出NullPointerException,根本轮不到你的统一错误处理器工作!

为什么会这样?因为Retrofit内部在分发错误时,会先检查你有没有传入自定义的onFailure实现,如果没有,它会尝试调用一个默认的空回调——但这个默认回调其实是null,直接调用就会触发空指针崩溃。而你的统一错误处理器(不管是用Interceptor还是自定义CallAdapter实现的),在这个空指针抛出后就被打断了,连日志都没机会输出。

解决方案

方案1:临时兜底——给每个请求补上空的onFailure回调

这是最快解决当前问题的方法,哪怕是空实现,也能避免崩溃,让统一错误处理器有机会接管错误:

apiService.getSpots()
    .enqueue(object : Callback<List<Spot>> {
        override fun onResponse(call: Call<List<Spot>>, response: Response<List<Spot>>) {
            // 处理正常响应逻辑
        }

        override fun onFailure(call: Call<List<Spot>>, t: Throwable) {
            // 空实现,或者直接调用统一错误处理器的处理方法
            // ErrorHandler.handleRequestError(t)
        }
    })

方案2:从根源解决——自定义CallAdapter封装默认错误回调

如果项目里请求很多,一个个加回调太麻烦,最好从封装层面统一处理,给所有请求自动加上安全的错误回调兜底:

class SafeCallAdapter<T>(private val delegate: CallAdapter<T, Call<T>>) : CallAdapter<T, Call<T>> {
    override fun responseType(): Type = delegate.responseType()

    override fun adapt(call: Call<T>): Call<T> {
        return object : Call<T> by call {
            override fun enqueue(callback: Callback<T>) {
                val safeCallback = object : Callback<T> {
                    override fun onResponse(call: Call<T>, response: Response<T>) {
                        callback.onResponse(call, response)
                    }

                    override fun onFailure(call: Call<T>, t: Throwable) {
                        // 先统一记录日志,再调用传入的回调(如果有的话)
                        Log.e("SafeCall", "请求失败: ${call.request().url()}", t)
                        try {
                            callback.onFailure(call, t)
                        } catch (e: NullPointerException) {
                            // 兜底防止回调为空
                        }
                    }
                }
                delegate.adapt(call).enqueue(safeCallback)
            }
        }
    }
}

// 对应的CallAdapterFactory
class SafeCallAdapterFactory : CallAdapter.Factory() {
    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        val delegate = retrofit.nextCallAdapter(this, returnType, annotations)
        return SafeCallAdapter(delegate as CallAdapter<Any, Call<Any>>)
    }
}

然后在构建Retrofit实例时添加这个Factory:

val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addCallAdapterFactory(SafeCallAdapterFactory())
    .addConverterFactory(GsonConverterFactory.create())
    .build()

这样所有请求都会自动带上安全的错误回调,不用再手动重复写onFailure了。

总结

这种问题特别隐蔽,因为你很容易想当然地认为统一错误处理器能兜底所有情况,但Retrofit的回调机制有个小前提:必须确保onFailure回调不为null。加空回调能快速解决眼前的崩溃,但从长远来看,统一封装CallAdapter才是更优雅的解决方案,能避免团队里其他人再踩同样的坑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:17:41