簡體   English   中英

為什么 `lifecycleScope.launch` 中的耗時操作會阻塞 UI 線程?

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

我從數據庫中讀取頁面列表並顯示在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"
    }
}

和章節列表片段:

    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方法中讀取數據庫的操作造成的,但是我是在協程中進行的,我不明白為什么仍然存在延遲。

不顯示與binding.hasChapters關聯的ProgressBar

如果我將Thread.sleep(2000)添加到getChapters方法,則ChapterListFragment將延遲兩秒打開。

所以我有兩個問題:

1. 為什么會這樣?

我原來的理解是在lifecycleScope.launch中執行的代碼並沒有阻塞當前的UI線程,但是現在看來並不是這樣。

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,也就是打開Fragment所需的動畫時間,所以沒有阻塞 UI 線程, ProgressBar可以正常顯示。 然后在主線程(UI線程)獲取分頁列表並刷新,沒有滯后。

但是我覺得這種方式不好,不應該主動阻塞300毫秒,然后去取列表。

這里有幾件事要考慮。

  1. 最好只在onCreateView中為View充氣,以便它盡可能快地充氣。
  2. onViewCreated膨脹后做其他與視圖相關的工作。
  3. 您正在使用withContext ,它不是必需的,因為它類似於async {...}.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)
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM