Android屏幕旋转时Fragment通过ViewModel填充的RecyclerView数据丢失如何解决
Android屏幕旋转后RecyclerView数据丢失解决方案
问题核心原因
你遇到的问题本质是Event包装类的一次性消费特性和配置变更后Fragment重建的逻辑冲突导致的:
- Event类的设计逻辑是单消费者一次性消费,首次Fragment拿到数据消费后,Event内部的
hasBeenHandled标记会被置为true - 屏幕旋转时Fragment销毁重建,新的Fragment实例重新订阅LiveData时,LiveData中存储的是已经被消费过的Event,EventObserver不会再触发回调,导致RecyclerView无数据填充
- 额外不合理逻辑:你在Observer回调中才初始化Adapter、配置RecyclerView的LayoutManager,每次收到数据都重复执行初始化操作,也会增加不必要的性能损耗
修复步骤
1. ViewModel层新增缓存字段,持久化存储图片列表
在ViewModel中额外存储一份全量的图片列表,生命周期和ViewModel绑定,不会随屏幕旋转销毁:
@HiltViewModel class ImagePickerViewModel @Inject constructor( private val imageListUseCase: ImageListUseCase, private val responseManager: ResponseManager ) : BaseViewModel() { // 新增:缓存最新的图片列表 private var currentImageList: ArrayList<Image> = arrayListOf() private val _observeImageListData = MutableLiveData<Event<ArrayList<Image>>>() init { getImageList(Constants.KEY_WORD) } private fun getImageList(keyWord: String) { responseManager.loading() val disposable = imageListUseCase.execute(keyWord, { success -> responseManager.hideLoading() // 先更新缓存再发送事件 currentImageList = success _observeImageListData.value = Event(success) }, { error -> responseManager.failed(error) }) compositeDisposable.add(disposable) } fun filterSearchKeyWord(filteredKeyWord: String) { if(filteredKeyWord.isNotEmpty()) getImageList(filteredKeyWord) } // 新增:对外暴露缓存的图片列表,供Fragment重建时直接读取 fun getCachedImageList(): ArrayList<Image> = currentImageList val observeImageListData: LiveData<Event<ArrayList<Image>>> get() = _observeImageListData }
2. Fragment层调整初始化逻辑,提前配置RecyclerView
把RecyclerView、Adapter的初始化逻辑提前到View创建阶段,重建时优先读取ViewModel的缓存填充列表:
@AndroidEntryPoint class ImagePickerFragment : Fragment() { private lateinit var imagePickerBinding: FragmentImagePickerBinding private val imagePickerViewModel: ImagePickerViewModel by activityViewModels() private lateinit var imagePickerAdapter: ImagePickerAdapter override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { imagePickerBinding = FragmentImagePickerBinding.inflate(inflater, container, false) // 先初始化RecyclerView相关配置 initRecyclerView() observeImageList() observeSearchText() return imagePickerBinding.root } private fun initRecyclerView() { // 初始化Adapter时优先读取ViewModel缓存,旋转重建时直接拿到历史数据 imagePickerAdapter = ImagePickerAdapter(imagePickerViewModel.getCachedImageList(), imagePickerViewModel) imagePickerBinding.apply { rvImageList.setHasFixedSize(true) rvImageList.layoutManager = GridLayoutManager(requireContext(),2) rvImageList.adapter = imagePickerAdapter } } private fun observeImageList() { imagePickerViewModel.observeImageListData.observe( viewLifecycleOwner, EventObserver { imageList -> // 仅更新Adapter数据,不重复执行初始化逻辑 imagePickerAdapter.updateData(imageList) }) } }
3. 给Adapter新增数据更新方法
在ImagePickerAdapter中新增更新数据的方法,避免每次都重新创建Adapter实例:
class ImagePickerAdapter( private var dataList: ArrayList<Image>, private val viewModel: ImagePickerViewModel ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { // 新增数据更新方法 fun updateData(newList: ArrayList<Image>) { dataList = newList notifyDataSetChanged() // 可接入DiffUtil实现局部刷新,提升性能 } // 其余原有Adapter逻辑保持不变 }
可选优化方案
图片列表属于状态类数据,本身不属于需要一次性消费的事件,你也可以直接去掉Event包装,从根源解决问题:
- 把
_observeImageListData的类型从MutableLiveData<Event<ArrayList<Image>>>改为MutableLiveData<ArrayList<Image>> - 去掉EventObserver,使用普通的Observer观察LiveData即可,屏幕旋转后Fragment重新订阅时,LiveData会自动回调最新的列表数据,不需要额外添加缓存字段
内容的提问来源于stack exchange,提问作者Ahmed Mamdouh
相关产品推荐
相关产品推荐

