PagingData 运行 默认情况下在 Paging 3 库中的后台线程上吗?

Does PagingData Run on a Background Thread by Default in Paging 3 Library?

是否像 PagedList 一样使用 PagingData 自动管理后台线程,然后在主线程上返回?

从下面的日志来看,与 Paging 2 库中的 PagedList 相比,Paging 3 库中的背景线程 PagingData 似乎不是 运行。

预期基于Paging Codelab样本

观察

分页 2

线程由 PagedListtoLiveData 根据 documentation 在后台处理。

If you use LivePagedListBuilder to get a LiveData, it will initialize PagedLists on a background thread for you.

分页 3

Paging 3 documentation 没有提到如何管理线程。但是,从日志来看,PagingSource 似乎是 运行 在主线程上发送网络请求并在主线程上返回 PagingData

我的示例代码

我在 CryptoTweets 示例应用 app-simple 模块中重新创建了 Codelab 模式。

FeedPagingSource.kt

class FeedPagingSource : PagingSource<Int, Tweet>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Tweet> {
        try {
            val page = params.key ?: 1
            val nextPage = page + 1
            val tweets = Injection.feedService.getTweets(...)
            println("Thread: FeedPagingSource ${Thread.currentThread().name}")
            Log.v(LOG_TAG, "load success: ${tweets}")
            return LoadResult.Page(...)
        } catch (error: Exception) {
            ...
        }
    }
}

FeedRepository.kt

class FeedRepository {
    fun initFeed() = Pager(
        config = PagingConfig(pageSize = FEED_PAGEDLIST_SIZE),
        pagingSourceFactory = { FeedPagingSource() }
    ).flow
}

FeedViewModel.kt

repository.initFeed().onEach {
    println("Thread: FeedViewModel ${Thread.currentThread().name}")
    _feed.value = it
}.launchIn(viewModelScope)

尝试的解决方案

为了在后台线程上 运行 PagingSource,流程在 Dispatchers.IO 上启动。但是在FeedPagingSource.kt.

的主线程上,日志仍然显示PagingSource 运行s

FeedViewModel.kt

repository.initFeed().onEach {
            println("Thread: FeedViewModel ${Thread.currentThread().name}")
            _feed.value = it
}.flowOn(Dispatchers.IO).launchIn(viewModelScope)

Kotlin 协程

当使用 Kotlin Coroutines 实现 PagingDataPagingSource 并发出 Retrofit 网络请求时,如上面的示例代码中,Retrofit 默认处理后台线程, returns 在主线程。

参见 Whosebug:Does Retrofit make network calls on main thread?

RxJava

当使用带有 Retrofit 的 RxJava 作为网络源时,需要明确指定线程,如 Android 文档示例中所示。

请参阅 Android 文档:Page from network and database > Implement a RemoteMediator

它不是 运行 在 Paging 3 的后台线程上,即使我在视图模型中使用 lifecycleScope.launch(Dispatchers.IO),因为当适配器加载时从主线程访问 PagingSource。所以对于 Room,我通过将 PagingSource 数据库代码包装在 withContext(Dispatchers.IO) {

中来让它工作
private const val STARTING_PAGE_INDEX = 0
private const val COUNT_PER_PAGE = 20

class LocalImagesPagingSource() : PagingSource<Int, GalleryImage>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, GalleryImage> {
        val page = params.key ?: STARTING_PAGE_INDEX
        return try {
            withContext(Dispatchers.IO) {
                val dao = GalleryImageDatabase.getDatabase(context).galleryImageDao()
                val images = dao.getGalleryImagesByPosition(page * COUNT_PER_PAGE)

                LoadResult.Page(
                    data = images,
                    prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
                    nextKey = if (images.size == 0) null else page + 1
                )
            }
        } catch (exception: IOException) {
            return LoadResult.Error(exception)
        }
    }
}