Navigation多返回栈如何实现不丢失Fragment视图状态、SavedInstanceState及关联ViewModel
popBackStack触发状态销毁的核心原因是回滚add/replace事务时,对应的Fragment实例被FragmentManager从宿主中完全移除,生命周期走到onDestroy,因此绑定到Fragment生命周期的状态、ViewModel都会被清空。可以根据业务场景选择以下解决方案:
方案1:使用hide/show手动管理Fragment切换,不加入返回栈
适用于多tab切换、需要长期保留所有加载过的Fragment状态的场景。不要用replace加载Fragment,也不要把切换事务加入返回栈,全程通过hide()、show()操作Fragment实例,自行拦截系统返回键处理返回逻辑。这种情况下所有Fragment实例不会被移除,视图状态、ViewModel都不会被销毁。
示例代码:// Fragment切换逻辑 supportFragmentManager.beginTransaction() .hide(currentVisibleFragment) .apply { val target = supportFragmentManager.findFragmentByTag(TargetFragment.TAG) if (target == null) { add(R.id.fragment_container, TargetFragment(), TargetFragment.TAG) } else { show(target) } } .commit()方案2:依赖系统默认的实例状态保存能力
若只是需要恢复基础视图状态(比如输入框内容、列表滚动位置),不需要保留Fragment的ViewModel实例,不需要修改架构:Android默认会在Fragment被移除前调用onSaveInstanceState()把视图状态存入返回栈记录,Fragment重建时会自动恢复这些状态。如果有自定义状态需要保存,手动把值写入该方法的Bundle参数,重建时从onCreate/onViewCreated的savedInstanceState参数读取即可。方案3:使用Jetpack Navigation的状态保存能力
若使用Jetpack Navigation组件,2.4.0及以上版本原生支持返回栈Fragment状态保存,配置NavOptions时开启saveState和restoreState即可,pop返回时会自动恢复Fragment的视图状态、ViewModel实例,无需额外处理。
示例代码:findNavController().navigate( R.id.action_fragment_a_to_fragment_b, null, NavOptions.Builder() .setPopUpTo(R.id.fragment_a, saveState = true) .setRestoreState(true) .build() )方案4:将状态存储在更长生命周期的载体中
若不可避免要触发Fragment销毁,可把需要保留的状态存储在生命周期比当前Fragment更长的载体中:- 业务相关的页面状态存在与宿主Activity/NavHostFragment绑定的ViewModel,这类ViewModel不会因为子Fragment的销毁而被清理
- 需要持久化的状态存入SharedPreferences、本地数据库等存储层,Fragment重建时读取恢复即可
方案5:精准控制回滚范围
提交Fragment事务时给addToBackStack()传入唯一标记,pop时调用popBackStack("标记名称", 标记位)精准指定要回滚的事务范围,避免误回滚需要保留状态的Fragment对应的事务。
注意:禁止强行持有Fragment实例引用来保留状态,会导致严重的内存泄漏,所有状态保留操作都应该使用官方提供的生命周期感知的方案实现。
内容的提问来源于stack exchange,提问作者Muhammad Zahab

