paging3加载功能中缓慢滚动

问题描述 投票:0回答:1

由于需要进行多个网络调用,我在滚动时遇到加载缓慢的情况。

class RepoResultPagingSource(
private val repository: Repository,
private val query: String
) : PagingSource<Int, Result>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Result> {
    try {
        val page = params.key ?: 1
        // network call to get repositories
        val repos = repository.getRepositories(query, page, PAGE_SIZE)

        val results = repos.map { repo ->
            // uses repo to get its top contributor
            val contributor = repository.getTopContributor(repo.owner.username, repo.reponame)
            Result(repo.owner.url, repo.reponame, contributor.username?: "")
        }
        return LoadResult.Page(
            data = repoResults,
            prevKey = if (page == 1) null else page-1,
            nextKey = page + 1
        )
    } catch (e: Exception) {
        return LoadResult.Error(e)
    }
}

class Repository(private val api: RepositoryApi,
             private val dispatcher: CoroutineDispatcher = Dispatchers.IO) {
    suspend fun getRepositories(query: String, page: Int, perPage: Int): List<Repository> 
    {
       return withContext(dispatcher) {
           api.search(query, page, perPage).repositories
       }
    }

    suspend fun getTopContributor(ownerName: String, repoName: String): Contributor {
        return withContext(dispatcher) {
            api.getContributors(ownerName, repoName, 1).first()
        }
    }
}

我意识到加载是阻塞的,因为分页库从 runBlockin 调用它

@JvmStatic
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public fun <K : Any, T : Any> create(
        pagingSource: PagingSource<K, T>,
        initialPage: PagingSource.LoadResult.Page<K, T>?,
        coroutineScope: CoroutineScope,
        notifyDispatcher: CoroutineDispatcher,
        fetchDispatcher: CoroutineDispatcher,
        boundaryCallback: BoundaryCallback<T>?,
        config: Config,
        key: K?
    ): PagedList<T> {
        val resolvedInitialPage = when (initialPage) {
            null -> {
                // Compatibility codepath - perform the initial load immediately, since caller
                // hasn't done it. We block in this case, but it's only used in the legacy path.
                val params = PagingSource.LoadParams.Refresh(
                    key,
                    config.initialLoadSizeHint,
                    config.enablePlaceholders,
                )
                runBlocking {
                    val initialResult = pagingSource.load(params)
                    when (initialResult) {
                        is PagingSource.LoadResult.Page -> initialResult
                        is PagingSource.LoadResult.Error -> throw initialResult.throwable
                        is PagingSource.LoadResult.Invalid ->
                            throw IllegalStateException(
                                "Failed to create PagedList. The provided PagingSource " +
                                    "returned LoadResult.Invalid, but a LoadResult.Page was " +
                                    "expected. To use a PagingSource which supports " +
                                    "invalidation, use a PagedList builder that accepts a " +
                                    "factory method for PagingSource or DataSource.Factory, " +
                                    "such as LivePagedList."
                            )
                    }
                }
            }
            else -> initialPage
        }
        return ContiguousPagedList(
            pagingSource,
            coroutineScope,
            notifyDispatcher,
            fetchDispatcher,
            boundaryCallback,
            config,
            resolvedInitialPage,
            key
        )
    }

所以我注入一个 viewModelScope 来启动一个非阻塞协程,比如

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Result> {
    try {
        return scope.async {
            val page = params.key ?: 1
            // network call to get repositories
            val repos = repository.getRepositories(query, page, 10)

            val results = repos.map { repo ->
                // uses repo to get its top contributor
                val contributor = repository.getTopContributor(repo.owner.username, 
repo.reponame)
                Result(repo.owner.url, repo.reponame, contributor.username?: "")
            }
            return@async LoadResult.Page(
                data = repoResults,
                prevKey = if (page == 1) null else page-1,
                nextKey = page + 1
            )
       }.await() as LoadResult<Int, Result>
    } catch (e: Exception) {
        return LoadResult.Error(e)
    }
}

不幸的是滚动仍然很慢,所以我问

1.尽管 getRepositories 和 getTopContributor 切换到 Dispatchers.IO,但默认情况下 load 是否确实以阻塞方式运行,根据 Why 'withContext' does not switch coroutines under 'runBlocking'? 是的

2.由于我需要发出 1 个网络请求才能获取存储库,并且每个存储库都需要找到其最大贡献者,因此我发出 page_size+1 请求。我正在寻找提高滚动性能的技巧,因为每个页面都依赖于这些网络请求。我的寻呼机看起来像

val pagingData: Flow<PagingData<RepoResult>> = Pager(
    config = PagingConfig(pageSize = PAGE_SIZE, prefetchDistance = 4 * PAGE_SIZE),
    pagingSourceFactory = { RepoResultPagingSource(repo, QUERY, viewModelScope) }
).flow.cachedIn(viewModelScope)
android performance kotlin-coroutines paging android-paging-3
1个回答
0
投票

不幸的是我无法写评论。 您是否尝试过在发布版本中运行它? 我在调试构建时遇到了同样的问题,交换期间也存在延迟。我搜索了很长时间的问题,整理了一个发布版本 - 一切都很好!

© www.soinside.com 2019 - 2024. All rights reserved.