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

Fragment中API请求时按返回键致App崩溃问题排查

问题分析

你的崩溃完全是生命周期相关的问题。当你按下返回键时,Fragment会被销毁并与Activity解除绑定,此时activity属性会变为null。但Retrofit的enqueue是异步请求——请求发起后会在后台执行,当请求完成回调onResponse/onFailure时,Fragment已经没有依附的Activity了,而你代码中使用了(activity as AppCompatActivity)这种非空强制类型转换,试图把null转成非空的AppCompatActivity,这就直接触发了kotlin.TypeCastException。

解决方案

我们需要从两个方面修复这个问题:一是避免在回调中访问已失效的Activity/Fragment组件,二是在Fragment销毁时取消未完成的请求,避免无用的回调执行。

1. 保存Retrofit Call实例,在Fragment销毁时取消请求

首先在Fragment中添加一个成员变量保存Retrofit的Call对象,这样可以在Fragment生命周期结束时取消请求,防止回调继续执行:

class ProductStockOutletListFragment : Fragment() {
    private var binding: FragmentProductStockOutletListBinding? = null
    // 保存Retrofit请求的Call实例
    private var outletListCall: Call<OutletListPOJODataClasses>? = null

    override fun onCreateView(...) {
        // ... 其他代码 ...

        // 给Call实例赋值
        outletListCall = NetworkConfig().getOutletListService()
            .getOutlets()
            .enqueue(object : Callback<OutletListPOJODataClasses> {
                // ... 回调实现 ...
            })
    }

    override fun onDestroyView() {
        super.onDestroyView()
        // 取消未完成的请求,避免回调执行
        outletListCall?.cancel()
        // 清空binding,防止内存泄漏
        binding = null
    }
}

2. 在回调中安全访问Activity和View组件

在onResponse和onFailure回调中,不要直接强制转换activity,而是先检查Fragment是否还处于活跃状态(依附于Activity),再使用安全调用处理:

修改后的回调代码:

override fun onFailure(call: Call<OutletListPOJODataClasses>, t: Throwable) {
    // 先检查请求是否是主动取消的,再判断Activity是否存在
    if (call.isCanceled) {
        activity?.let {
            Toast.makeText(it, "Request Aborted", Toast.LENGTH_SHORT).show()
        }
    } else {
        activity?.let {
            Toast.makeText(it, t.localizedMessage ?: "Unknown error", Toast.LENGTH_SHORT).show()
        }
    }
}

override fun onResponse(
    call: Call<OutletListPOJODataClasses>,
    response: Response<OutletListPOJODataClasses>
) {
    // 先检查Fragment是否还依附于Activity,以及binding是否有效
    if (!isAdded || binding == null) return

    binding?.progressBar?.visibility = View.GONE
    response.body()?.let { outletList ->
        binding?.rvOutletList?.adapter = OutletListAdapter(outletList, this@ProductStockOutletListFragment)
    }
    // 安全显示Toast
    activity?.let {
        Toast.makeText(it, "Data retrieved!", Toast.LENGTH_SHORT).show()
    }
}

额外优化:避免重复强制转换Activity

你代码中多次出现(activity as AppCompatActivity),可以用requireActivity()替代,但注意requireActivity()在Activity为null时会抛出异常,所以必须先确保Fragment处于活跃状态(用isAdded判断),或者直接用activity的安全调用。

为什么这能解决问题?
  • 取消未完成的请求:当Fragment销毁时,调用call.cancel()会终止Retrofit的请求,回调不会再执行,从根源上避免了访问失效组件的可能。
  • 安全访问组件:通过isAdded检查Fragment状态,结合activity?.let和binding?.的安全调用,避免了null值的强制转换,彻底消除了TypeCastException的触发条件。

内容的提问来源于stack exchange,提问作者Foxtrot 1-5

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 20:17:38