为什么需要分页
了解一次性加载全部数据的代价
为什么需要分页 是 CoddyKit 上的免费 Android Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。
一次性加载全部数据代价高昂
想象一个包含 50,000 个项目的信息流。如果一次性获取并保存全部项目,就会浪费内存、拖慢网络,并且在解析响应时冻结界面。
大多数用户实际上只会滚动浏览最前面的几屏内容。分页是指用户滚动时分小块(页面)加载数据,而不是一开始就全部加载。
代码中的问题
一种简单的做法是将完整列表加载到内存中。即使服务器支持这样做,响应也可能非常庞大,而解析后的列表会占用堆中的大部分空间。
这种模式无法很好地扩展,在处理大型数据集时还可能引发 OutOfMemoryError。
// Anti-pattern: load the whole table at once
suspend fun loadAllArticles(): List<Article> {
// Could be tens of thousands of rows / megabytes of JSON
return api.getArticles(limit = 50_000)
}
// UI holds ALL of them in memory at once
val articles = loadAllArticles()
LazyColumn {
items(articles) { article -> ArticleRow(article) }
}页面的构成
页面是数据集中的一小部分,通常包含 20 到 50 个项目。服务器会返回一个页面,以及指向下一页的指针(数字、偏移量或令牌)。
应用只在内存中保留少量页面;当用户滚动离开旧页面后,应用会将其丢弃。
data class ArticlePage(
val items: List<Article>,
val nextKey: Int? // null means no more pages
)
// Example REST call returning one page
suspend fun getArticlePage(page: Int, size: Int = 20): ArticlePage认识 Paging 3
Paging 3 是 Jetpack 中负责处理分页所有复杂工作的库:
- 当用户接近列表末尾时请求下一页
- 内存中只保留一个项目窗口
- 提供加载状态和错误状态
- 内置对协程、Flow 和 Jetpack Compose 的支持
添加依赖项
Paging 3 以独立构件的形式提供:一个运行时构件,以及一个 Compose 集成构件。请在模块的 build.gradle.kts 中添加它们。
paging-compose 构件为 LazyColumn 提供了辅助工具。
// build.gradle.kts (module)
dependencies {
val pagingVersion = "3.3.6"
implementation("androidx.paging:paging-runtime:$pagingVersion")
implementation("androidx.paging:paging-compose:$pagingVersion")
}三个核心组成部分
Paging 3 围绕以下三种类型展开,您会反复使用它们:
- PagingSource - 知道如何从数据源加载一个页面
- Pager - 配置并生成分页数据流
- PagingData - 存放项目的容器,以
Flow的形式发出
接下来的课程将分别构建这些部分。
PagingData 与 Flow
您的存储库会公开一个 Flow<PagingData<T>>。界面收集这个数据流,并呈现当前已加载的项目。
您不需要亲自组装完整列表 - Paging 会按需将项目流式传送到界面。
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
class ArticleRepository(private val api: ArticleApi) {
fun articles(): Flow<PagingData<Article>> = Pager(
config = PagingConfig(pageSize = 20)
) {
ArticlePagingSource(api)
}.flow
}PagingConfig 控制数据窗口
PagingConfig 用于调整 Paging 加载和保存数据的方式:
pageSize- 每次页面请求包含的项目数prefetchDistance- 距离边缘多远时触发下一次加载enablePlaceholders- 为尚未加载的项目显示占位槽maxSize- 内存中保留的项目数上限
import androidx.paging.PagingConfig
val config = PagingConfig(
pageSize = 20,
prefetchDistance = 5,
enablePlaceholders = false,
maxSize = 100
)网络、数据库,还是两者兼用
PagingSource 可以从 REST API、Room 数据库或任何自定义数据源获取数据。
如果要实现真正的离线优先体验,可以通过 RemoteMediator 将两者结合起来(最后一课会介绍):网络填充本地数据库,界面则从该数据库中分页读取数据。
滚动更流畅,占用内存更少
使用分页后,每次请求的数据量都很小,并且内存中的窗口受 maxSize 限制,因此滚动会保持流畅。
借助 LoadState,加载指示器和错误指示器可以直接使用,因此无需手动记录状态,就能在底部显示加载动画或重试按钮。
// LoadState exposes loading/error per direction
when (val state = adapterLoadState.append) {
is LoadState.Loading -> showBottomSpinner()
is LoadState.Error -> showRetry(state.error)
is LoadState.NotLoading -> hideBottomSpinner()
}何时需要分页
在以下情况下可以使用 Paging 3:
- 数据集很大或没有明确上限(信息流、搜索结果、聊天记录)
- 数据来自会返回页面或游标的网络 API
- 您希望自动处理加载/错误界面和内存控制
对于简短且固定的列表(例如设置菜单),普通的 LazyColumn 更简单,也完全够用。
快速检查
对于大型列表,使用 Paging 3 的主要好处是什么?
回顾:为什么使用分页
您已经了解了一次性加载所有数据为什么会损害性能并占用大量内存,以及 Paging 3 如何解决这个问题。
- 用户滚动时,数据会以较小的页面逐步加载
- 三个核心组成部分是 PagingSource、Pager 和 PagingData
PagingConfig用于调整页面大小、预取距离和内存窗口- Paging 能够与协程、Flow 和 Compose 集成,并免费提供加载/错误状态
下一步:构建 PagingSource 并将其连接到 Pager。
用 AI 导师学习 Kotlin — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 36
- 课程
- 152
常见问题解答
「为什么需要分页」课时是免费的吗?
是的 — 「为什么需要分页」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。
「为什么需要分页」这节课中我会学到什么?
了解一次性加载全部数据的代价 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Android Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「为什么需要分页」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Android Academy 课中编写并运行代码吗?
能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。