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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 07:12:27