Kotlin Retrofit回调response.body()空指针异常解决方案
问题现象
Android开发场景下,通过Retrofit请求API数据并传入RecyclerView适配器时,首次进入页面数据可正常加载展示;点击RecyclerView列表项跳转至其他Activity,再返回原Activity时应用崩溃,崩溃日志如下:
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.marcusneo.stockportfolio, PID: 7586 java.lang.NullPointerException at com.marcusneo.stockportfolio.MainActivity$getPortData$1.onResponse(MainActivity.kt:191) at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1
崩溃指向代码行:response.body()!!.annualReports.let { detailAdapter.setData(it) }
根因分析
现有代码虽然加了response.isSuccessful和response.body() != null判断,但依然触发空指针,核心原因有三个:
- 重复调用
response.body()导致空值:Retrofit的Response.body()方法本质是读取响应流,流仅可被消费一次,第一次调用判断非空后,第二次调用response.body()时流已经被消费,会直接返回null,此时用!!强制非空就会触发空指针。 - 异步回调生命周期不安全:跳转Activity再返回的过程中,原Activity可能经历销毁重建,Retrofit的异步请求回调如果在页面销毁后才返回,此时操作UI、适配器也会触发异常。
- 额外逻辑问题:代码在
enqueue异步请求外部直接调用detailAdapter.notifyDataSetChanged(),此时请求还未返回、数据未更新,属于无效执行。
修复方案
按以下规则调整代码即可彻底避免该崩溃:
- 仅调用一次
response.body(),将返回值存入局部临时变量复用,禁止重复调用消费响应流 - 异步回调执行时先判断Activity是否处于活跃状态,页面已销毁的话直接返回,不执行后续UI操作
- 全程使用Kotlin安全调用操作符处理空值,彻底移除
!!非空断言 - 仅在适配器数据更新完成后再调用
notifyDataSetChanged(),移除无效的提前刷新调用
修复后的完整代码如下:
private fun getDetailsData(keywords: String){ val retrofitData = retrofitBuilder.getCompDetails(keywords) retrofitData.enqueue(object : Callback<IncomeState?> { @SuppressLint("NotifyDataSetChanged") override fun onResponse(call: Call<IncomeState?>, response: Response<IncomeState?>) { // 页面已销毁直接返回,不执行后续UI操作 if (isFinishing || isDestroyed) return if(response.isSuccessful){ // 仅读取一次body,存入局部常量复用 val body = response.body() if(body != null){ val reports = body.annualReports if (!reports.isNullOrEmpty()) { detailAdapter.setData(reports) detailAdapter.notifyDataSetChanged() binding.loadingDetail.visibility = View.GONE binding.rvDetails.visibility = View.VISIBLE } else { binding.loadingDetail.visibility = View.VISIBLE binding.rvDetails.visibility = View.GONE } } else { binding.loadingDetail.visibility = View.VISIBLE binding.rvDetails.visibility = View.GONE } } else { Log.d("Response Error", response.errorBody()?.toString() ?: "empty error msg") } } override fun onFailure(call: Call<IncomeState?>, t: Throwable) { // 页面已销毁直接返回 if (isFinishing || isDestroyed) return Log.d("MainActivity","onFailure: " + t.message) } }) }
注意:Kotlin异步回调场景下禁止随意使用
!!非空断言,异步任务返回时机不可控,上下文状态随时可能变化,优先使用?.安全调用、?:空值兜底、局部变量存储非空值的方式实现空安全。
内容的提问来源于stack exchange,提问作者Marcus Neo
相关产品推荐
相关产品推荐

