RXJava如何统一处理缺失错误监听器/日志中缺失堆栈追踪问题
我最近在负责的项目里踩了个非常隐蔽的大坑——原本依赖的统一错误处理器完全没按预期工作,导致一堆难排查的问题。这次遇到的情况更头疼:某个非关键的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

