为什么“lifecycleScope.launch”中耗时的操作会阻塞 UI 线程?

Why do time-consuming operations in `lifecycleScope.launch` block the UI thread?

提问人:excing 提问时间:6/26/2022 最后编辑:excing 更新时间:6/26/2022 访问量:543

问:

我从数据库中读取页面列表并显示在 .它看起来像这样:RecyclerView

视图模型:

@HiltViewModel
class BookDetailViewModel @Inject internal constructor(
    savedStateHandle: SavedStateHandle,
    private val bookRepository: BookRepository,
    private val chapterRepository: ChapterRepository,
) : ViewModel() {
    private var currentResult: Flow<PagingData<Chapter>>? = null

    val bookID: Long = savedStateHandle.get<Long>(BOOK_ID_SAVED_STATE_KEY)!!

    val book = bookRepository.getBook(bookID)

    suspend fun getChapters(): Flow<PagingData<Chapter>> {
        val lastChapterID = book.first().lastChapterID
        val newResult = chapterRepository
            .getChapters(bookID, lastChapterID)
            .cachedIn(viewModelScope)
        currentResult = newResult
        return newResult
    }

    companion object {
        private const val BOOK_ID_SAVED_STATE_KEY = "bookID"
    }
}

和 ChapterListFragment:

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        ...
        ...
        
        load()

        return binding.root
    }
    private fun load() {
        // Make sure we cancel the previous job before creating a new one
        queryJob?.cancel()
        queryJob = lifecycleScope.launch {
            viewModel.getChapters().collectLatest { result ->
                binding.hasChapters = true
                adapter.submitData(lifecycle, result)
            }
        }
    }

但是,在打开时,有一小段滞后,显然是卡住了。ChapterListFragment

我认为这是由于方法中读取数据库的操作造成的,但是我是在协程中进行的,我不明白为什么仍然存在滞后。viewModel.getChapters

不显示关联的。ProgressBarbinding.hasChapters

如果我在方法中添加一个,那么将以两秒钟的延迟打开。Thread.sleep(2000)getChaptersChapterListFragment

所以我有两个问题:

1. 为什么会这样?

我最初的理解是执行的代码不会阻塞当前的 UI 线程,但现在看来情况并非如此。lifecycleScope.launch

2. 如何正确刷新我的列表?

编辑

我使用以下代码,然后打开变得顺畅:ChapterListFragment

    private fun load() {
        // Make sure we cancel the previous job before creating a new one
        queryJob?.cancel()
        queryJob = lifecycleScope.launch(Dispatchers.IO) {
            Thread.sleep(300)
            withContext(Dispatchers.Main) {
                viewModel.getChapters().collectLatest { result ->
                    binding.hasChapters = true
                    adapter.submitData(lifecycle, result)
                }
            }
        }
    }

我先阻塞了IO线程300ms,这是打开IO线程所需的动画时间,所以不会阻塞UI线程,可以正常显示。然后在主线程(UI线程)中获取分页列表并刷新,没有滞后。FragmentProgressBar

但我觉得这样不好,我不应该主动阻塞300毫秒,然后去获取列表。

Android IO kotlin-coroutines android-viewmodel android-database

评论

0赞 Darshan 6/26/2022
您是否尝试过将呼叫移至 ?此外,不是必需的,因为您不会返回任何结果。load()onViewCreatedwithContext
0赞 excing 6/28/2022
是的,我试过了。但结果与 中相同。onCreateView
0赞 Muhammad Babar 2/3/2023
面临同样的问题。

答:

0赞 Darshan 6/26/2022 #1

这里需要考虑一些事项。

  1. 最好只给 a 充气,这样它就会尽快充气。ViewonCreateView
  2. 在通货膨胀后做其他与视图相关的工作。onViewCreated
  3. 您正在使用 which 不是必需的,因为它类似于用于在块内进行某些处理后返回某些内容。(这“可能”会导致延迟,但不确定)withContextasync {...}.await

由于您正在使用必须处理生命周期的位置,因此您可以执行如下操作:Flow

// Follows the inflated view's lifecycle
viewLifecycleOwner.lifecycleScope.launch {
    // repeatOnLifecycle is a suspend function
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.getChapters().collectLatest { result ->
            binding.hasChapters = true
            adapter.submitData(lifecycle, result)
        }
    }
}

评论

0赞 excing 6/28/2022
无法找到该方法。repeatOnLifecycle
0赞 Darshan 6/28/2022
添加依赖项。lifecycle-runtime-ktx
0赞 excing 6/28/2022
我导入了包,它现在可以工作了。但还是有滞后,不够流畅,结果和修改前一样。lifecycle-runtime-ktx