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

Android 13中Fragment导航触发onPause/onResume循环致栈溢出求助

Android 13下NavController导航引发Fragment onPause/onResume无限循环问题排查

我的应用通过NavController实现Fragment间导航,核心导航代码为findNavController().navigate(R.id.step02Fragment),该逻辑在Android 13以下版本均正常运行。但在Android 13系统中,从Step05Fragment跳转至Step06Fragment时,Step05Fragment的onPause与onResume方法会无限循环执行,最终引发StackOverflow错误。其他Fragment采用完全相同的导航逻辑却未出现此问题。

依赖版本

implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-service:2.5.1"

implementation "androidx.navigation:navigation-runtime-ktx:2.5.2"
implementation "androidx.navigation:navigation-fragment-ktx:2.5.2"
implementation "androidx.navigation:navigation-ui-ktx:2.5.2"
implementation "androidx.navigation:navigation-dynamic-features-fragment:2.5.2"

Step05Fragment代码

@AndroidEntryPoint
class Step05Fragment : Fragment() {

    lateinit var binding: FragmentStep05Binding

    private var initialSetup: Boolean = true
    private val disposable = CompositeDisposable()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentStep05Binding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        arguments?.getBoolean(Constants.ENABLE_PERMISSION)?.let {
            initialSetup = it
        }

        setObservables()
        setView()
    }

    override fun onDestroy() {
        super.onDestroy()
        disposable.dispose()
    }

    private fun setObservables() {
        binding.btnEnable.clicks()
            .map {
                checkPermission()
            }
            .subscribe()
            .disposeBy(disposable)
    }

    private fun setView() {
        if (!initialSetup) {
            binding.currentStep.visibility = View.GONE
        }
    }

    private fun checkPermission() {
        if (!PermissionsUtils.isReadExternalStoragePermissionGranted(requireContext())) {
            requestPermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
        } else {
            handleNavigation()
        }
    }

    private val requestPermission =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            if (isGranted) {
                handleNavigation()
            } else {
                checkPermission()
            }
        }

    private fun handleNavigation() {
        if (initialSetup) {
            findNavController().navigate(R.id.step06Fragment)
        } else {
            activity?.finishAffinity()
        }
    }
}

Step06Fragment代码

@AndroidEntryPoint
class Step06Fragment : Fragment() {

lateinit var binding: FragmentStep06Binding

private var initialSetup: Boolean = true
private val disposable = CompositeDisposable()

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    binding = FragmentStep06Binding.inflate(inflater, container, false)
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    arguments?.getBoolean(Constants.ENABLE_PERMISSION)?.let {
        initialSetup = it
    }

    setObservables()
    setView()
}

override fun onDestroy() {
    super.onDestroy()
    disposable.dispose()
}

private fun setObservables() {
    binding.btnEnable.clicks()
        .map {
            checkPermission()
        }
        .subscribe()
        .disposeBy(disposable)
}

private fun setView() {
    if (!initialSetup) {
        binding.currentStep.visibility = View.GONE
    }
}

private fun checkPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        if (!PermissionsUtils.isMediaLocationPermissionGranted(requireContext())) {
            requestPermission.launch(Manifest.permission.ACCESS_MEDIA_LOCATION)
        } else {
            handleNavigation()
        }
    } else {
        handleNavigation()
    }
}

private val requestPermission =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        if (isGranted) {
            handleNavigation()
        } else {
            checkPermission()
        }
    }

private fun handleNavigation() {
    if (initialSetup) {
        findNavController().navigate(R.id.step07Fragment)
    } else {
        activity?.finishAffinity()
    }
}
}

排查思路

  • 权限请求循环触发问题:Step05Fragment中,当权限被拒绝时,requestPermission回调直接调用checkPermission(),会再次启动权限请求。Android 13对权限弹窗的生命周期处理有变化,这种重复请求可能干扰Fragment导航的正常生命周期,导致Step05Fragment无法正确转入后台,进而引发onPause/onResume循环。建议在权限拒绝时添加用户提示,而非直接重复请求。
  • 导航执行时机验证:在handleNavigation()中执行导航前,先判断Fragment状态,比如添加if (isResumed() && isAdded()),避免在Fragment生命周期不稳定时触发导航动作。
  • 升级Navigation组件版本:当前使用的Navigation 2.5.2版本可能存在Android 13兼容性问题,尝试升级到2.7.x或更高稳定版本,确认是否修复了相关生命周期异常。
  • 隔离Step06Fragment影响:暂时注释Step06Fragment中的权限请求逻辑,测试跳转后是否还会出现循环。如果问题消失,说明Step06Fragment的权限逻辑在Android 13下触发了异常回调,间接影响了Step05Fragment的生命周期。
  • 检查导航配置:查看导航图中Step05到Step06的action配置,是否设置了错误的popUpTo或launchSingleTop属性,导致Fragment实例重复创建或回栈异常。
  • 生命周期日志追踪:在Step05Fragment的onPause()、onResume()、handleNavigation()等关键方法中添加日志,输出当前状态和调用栈,明确循环触发的具体触发点,定位是导航重复执行还是生命周期被异常唤起。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 18:50:25