Android中ViewModel数据加载延迟及while循环引发主线程阻塞问题
问题原因分析
你添加的while循环会卡住主线程,核心原因如下:
- 这个while循环运行在Android主线程,是同步阻塞操作,会一直霸占主线程资源,导致主线程的消息队列完全无法处理其他任务——包括你通过
postDelayed提交的回调、UI渲染、甚至ViewModel里协程完成后的数据更新。 oeuvre的赋值操作在IO线程的协程中执行,就算数据库查询完成、oeuvre被成功赋值,主线程被while循环卡死,根本没机会读取更新后的oeuvre值,只会一直循环判断null,直到触发应用无响应(ANR)或等待很久才可能退出循环。- 你用
postDelayed提交的重试任务会被放到主线程消息队列,但while循环一直在占用主线程,这些任务永远得不到执行,重试逻辑完全无效,反而加重了主线程负担。
另外,最初oeuvreDetailViewModel.oeuvre?.title返回null的本质原因是:getOeuvre是异步执行的数据库查询,在onCreateView执行时,IO线程的查询还没完成,oeuvre自然还是初始的null值,这是异步加载的正常场景,不能用同步轮询的方式解决。
正确解决方案
要解决这个问题,应该用Android官方推荐的观察者模式(LiveData/Flow),让数据加载完成后自动通知UI更新,而非主动轮询。
第一步:修改ViewModel,用LiveData包装数据
把oeuvre改成MutableLiveData,让异步加载完成后自动通知UI:
class OeuvreDetailViewModel(application: Application, private var oeuvreId: Int): AndroidViewModel(application) { private var isArt = false; private var isPlaces = false; private var isHeritages = false; private val repository: OeuvreRepository // 内部用MutableLiveData,对外暴露不可变的LiveData private val _oeuvre = MutableLiveData<Oeuvre?>() val oeuvre: LiveData<Oeuvre?> = _oeuvre init { val oeuvreDao = OeuvreDatabase.getDatabase( application, viewModelScope ).oeuvreDAO() repository = OeuvreRepository.getInstance(oeuvreDao) getOeuvre() } private fun getOeuvre() { viewModelScope.launch(Dispatchers.IO) { val fetchedOeuvre = repository.getArticleById(oeuvreId) // 切换回主线程更新LiveData withContext(Dispatchers.Main) { _oeuvre.value = fetchedOeuvre } } } fun updateTarget(oeuvreId: Int, target: Int?) { viewModelScope.launch(Dispatchers.IO) { repository.updateTarget(oeuvreId,target) } } }
第二步:修改Fragment代码,去掉while循环,观察LiveData变化
借助DataBinding的特性,只要绑定了lifecycleOwner,LiveData的数据变化会自动更新UI,无需手动刷新:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { oeuvreDetailViewModel = ViewModelProvider( this, OeuvreDetailViewModelFactory(requireActivity().application, safeArgs.itemSelected.id) ).get(OeuvreDetailViewModel::class.java) val binding = DataBindingUtil.inflate<FragmentOeuvreItemBinding>(inflater, R.layout.fragment_oeuvre_item, container, false).apply { viewModel = oeuvreDetailViewModel lifecycleOwner = viewLifecycleOwner callback = getProperCallback(fab) toolbar.setNavigationOnClickListener { view -> view.findNavController().navigateUp() } } // 可选:如果需要在数据加载完成后执行额外逻辑,可在这里观察LiveData oeuvreDetailViewModel.oeuvre.observe(viewLifecycleOwner) { fetchedOeuvre -> fetchedOeuvre?.let { // 数据加载完成,比如打日志、执行其他业务逻辑 } } return binding.root }
这样修改后,当数据库查询完成、oeuvre数据更新时,LiveData会自动通知UI刷新,既不会阻塞主线程,也能保证UI拿到最新的非null数据。
内容的提问来源于stack exchange,提问作者Biskweet
相关产品推荐
相关产品推荐

