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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 15:19:32