Activity横竖屏切换时如何保留Fragment实例及Retrofit查询结果
横竖屏切换Fragment数据丢失解决方案
核心问题分析
你当前遇到的问题主要由三个错误实现导致:
- ViewModel作用域不统一:MenuActivity中初始化的是Activity级别的InformationViewModel,而OptionsFragment、TradingFragment中初始化的是Fragment级别的ViewModel,三者拿到的是完全不同的实例,请求得到的数据存在Fragment私有的ViewModel中,Fragment随横竖屏销毁时数据同步丢失。
- 每次重建都新建Fragment实例:Activity横竖屏重建时,你没有判断savedInstanceState状态,每次都强制replace新的Fragment实例,原有Fragment的状态被完全覆盖。
- ViewModel存储Fragment实例易引发内存泄漏:ViewModel生命周期长于Activity/Fragment,持有Fragment实例会导致销毁的Fragment无法被GC回收,存在内存溢出风险。
具体修改方案
1. 统一ViewModel作用域为Activity级别
所有场景下获取InformationViewModel时,都绑定到MenuActivity实例,保证整个页面生命周期内只有一个ViewModel实例,数据不会随Fragment销毁丢失:
- OptionsFragment中ViewModel初始化代码修改为:
// 原代码:ViewModelProvider(this).get(InformationViewModel::class.java) informationViewModel = ViewModelProvider(requireActivity()).get(InformationViewModel::class.java)
- TradingFragment中ViewModel初始化代码同步修改为:
informationViewModel = ViewModelProvider(requireActivity()).get(InformationViewModel::class.java)
2. 调整Activity的Fragment挂载逻辑
删除InformationViewModel中存储Fragment实例的变量,新增currentPage字符串/枚举变量标记当前显示的页面类型,同时只有首次创建Activity时才主动挂载Fragment,横竖屏重建时由系统自动恢复已有Fragment:
// InformationViewModel中新增变量 var currentPage: String = "options" // 可选值options/personal/trading
修改MenuActivity的onCreate逻辑:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMenuBinding.inflate(layoutInflater) setContentView(binding.root) informationViewModel = ViewModelProvider(this).get(InformationViewModel::class.java) // 只有首次创建时才挂载Fragment,重建时系统自动恢复 if (savedInstanceState == null) { val targetFragment = when(informationViewModel.currentPage) { "options" -> OptionsFragment() "personal" -> PersonalInfFragment() "trading" -> TradingFragment() else -> OptionsFragment() } showFragment(targetFragment) } }
同时删除原来的onPassFragment回调逻辑,Fragment切换时直接修改ViewModel的currentPage标记即可。
3. TradingFragment的UI状态持久化
把TradingFragment中需要保留的参数(选择的时间范围、下拉框选中值)同步存储到ViewModel中,重建时读取ViewModel的值恢复UI:
首先在InformationViewModel中新增状态变量:
var tradingFrom: Int = 0 var tradingTo: Int = 0 var selectedCurrencyPair: String = "" // 原有请求交易数据的LiveData保持不变即可
修改TradingFragment的相关逻辑:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // ... 原有控件初始化代码保持不变 informationViewModel = ViewModelProvider(requireActivity()).get(InformationViewModel::class.java) // 恢复之前保存的时间选择状态 from = informationViewModel.tradingFrom to = informationViewModel.tradingTo if (from != 0 && to != 0) { changeDateValueForText(from, to) rangeSeek.setValues(from.toFloat(), to.toFloat()) } else { val cur_time = "${System.currentTimeMillis()}".substring(0, 10) rangeSeek.valueTo = Integer.valueOf(cur_time) * 1.0f } // 恢复下拉框选中状态 if (informationViewModel.selectedCurrencyPair.isNotEmpty()) { val adapter = spinner.adapter for (i in 0 until adapter.count) { if (adapter.getItem(i).toString() == informationViewModel.selectedCurrencyPair) { spinner.setSelection(i) break } } } // ... 原有监听、RecyclerView初始化逻辑保持不变 // LiveData observe后会自动收到ViewModel中保存的历史数据,直接刷新列表即可 } private fun changeDateValueForText(start: Int, finish: Int) { from = start to = finish // 同步保存到ViewModel informationViewModel.tradingFrom = start informationViewModel.tradingTo = finish // ... 原有时间格式化逻辑保持不变 } private fun startSearchingCurrencyTrading() { val pair = spinner.selectedItem.toString() // 同步保存选中的币种对 informationViewModel.selectedCurrencyPair = pair informationViewModel.startRequestingCurrency(pair, from, to) }
可选优化
如果需要避免每次切换Fragment都新建实例,可以在挂载Fragment前先通过supportFragmentManager.findFragmentByTag()查找已存在的Fragment实例,有就复用,没有再新建,能进一步提升切换性能。
内容的提问来源于stack exchange,提问作者Bekzhan
相关产品推荐
相关产品推荐

