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样本)
- GithubPagingSource
override suspend fun load(...)
到 运行 在 IO 线程上。
- SearchRepositoriesActivity
viewModel.searchRepo(query).collectLatest { ... }
到 运行 在主线程上。
观察
- GithubPagingSource
override suspend fun load(...)
和 SearchRepositoriesActivity viewModel.searchRepo(query).collectLatest { ... }
运行 都在主线程上。
分页 2
线程由 PagedList
和 toLiveData
根据 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 实现 PagingData
和 PagingSource
并发出 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)
}
}
}
是否像 PagedList
一样使用 PagingData
自动管理后台线程,然后在主线程上返回?
从下面的日志来看,与 Paging 2 库中的 PagedList
相比,Paging 3 库中的背景线程 PagingData
似乎不是 运行。
预期(基于Paging Codelab样本)
- GithubPagingSource
override suspend fun load(...)
到 运行 在 IO 线程上。 - SearchRepositoriesActivity
viewModel.searchRepo(query).collectLatest { ... }
到 运行 在主线程上。
观察
- GithubPagingSource
override suspend fun load(...)
和 SearchRepositoriesActivityviewModel.searchRepo(query).collectLatest { ... }
运行 都在主线程上。
分页 2
线程由 PagedList
和 toLiveData
根据 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 实现 PagingData
和 PagingSource
并发出 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)
}
}
}