Paging 2 库概览 属于 Android Jetpack。
Paging 库可帮助您一次加载和显示小块数据。按需加载部分数据可减少网络带宽和系统资源的用量。
本指南提供了该库的几个概念性示例,以及其工作原理的概览。如需查看该库功能的完整示例,请尝试其他资源部分中的 Codelab 和示例。
设置
如需将 Paging 组件导入您的 Android 应用,请将以下依赖项添加到您应用的 build.gradle
文件中
Groovy
dependencies { def paging_version = "2.1.2" implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx // alternatively - without Android dependencies for testing testImplementation "androidx.paging:paging-common:$paging_version" // For Kotlin use paging-common-ktx // optional - RxJava support implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktx }
Kotlin
dependencies { val paging_version = "2.1.2" implementation("androidx.paging:paging-runtime:$paging_version") // For Kotlin use paging-runtime-ktx // alternatively - without Android dependencies for testing testImplementation("androidx.paging:paging-common:$paging_version") // For Kotlin use paging-common-ktx // optional - RxJava support implementation("androidx.paging:paging-rxjava2:$paging_version") // For Kotlin use paging-rxjava2-ktx }
库架构
本部分介绍并展示了 Paging 库的主要组件。
PagedList
Paging 库的关键组件是 PagedList
类,它会加载应用的数据块,即“页面”。当需要更多数据时,会将其分页到现有的 PagedList
对象中。如果任何已加载数据发生变化,则会从 LiveData
或基于 RxJava2 的对象向可观察数据持有者发出 PagedList
的新实例。当生成 PagedList
对象时,您应用的界面会呈现其内容,同时遵守您的 UI 控制器的生命周期。
以下代码段显示了如何配置您应用的视图模型,以使用 PagedList
对象的 LiveData
持有者加载和呈现数据
Kotlin
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData<PagedList<Concert>> = concertDao.concertsByDate().toLiveData(pageSize = 50) }
Java
public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final LiveData<PagedList<Concert>> concertList; // Creates a PagedList object with 50 items per page. public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new LivePagedListBuilder<>( concertDao.concertsByDate(), 50).build(); } }
数据
每个 PagedList
实例都从其对应的 DataSource
对象加载应用数据的最新快照。数据从应用后端或数据库流入 PagedList
对象。
以下示例使用 Room 持久性库来组织您的应用数据,但如果您想使用其他方式存储数据,也可以提供自己的数据源工厂。
Kotlin
@Dao interface ConcertDao { // The Int type parameter tells Room to use a PositionalDataSource object. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert> }
Java
@Dao public interface ConcertDao { // The Integer type parameter tells Room to use a // PositionalDataSource object. @Query("SELECT * FROM concerts ORDER BY date DESC") DataSource.Factory<Integer, Concert> concertsByDate(); }
如需详细了解如何将数据加载到 PagedList
对象中,请参阅关于如何加载分页数据的指南。
UI
PagedList
类与 PagedListAdapter
协同工作,将项目加载到 RecyclerView
中。这些类协同工作,在内容加载时获取并显示内容,预取视图外内容并为内容更改设置动画。
如需了解更多信息,请参阅关于如何显示分页列表的指南。
支持不同的数据架构
Paging 库支持以下数据架构
- 仅从后端服务器提供。
- 仅存储在设备数据库中。
- 其他来源的组合,使用设备数据库作为缓存。
图 1 显示了在每种架构场景中数据如何流动。在仅网络或仅数据库解决方案的情况下,数据直接流入您应用的 UI 模型。如果您使用的是组合方法,数据会从后端服务器流向设备数据库,然后再流向您应用的 UI 模型。每个数据流的端点偶尔会用完要加载的数据,此时它会从提供数据的组件请求更多数据。例如,当设备数据库用完数据时,它会从服务器请求更多数据。

本节的其余部分提供了配置每个数据流用例的建议。
仅网络
要显示后端服务器的数据,请使用 Retrofit API 的同步版本将信息加载到您自己的自定义 DataSource
对象中。
仅数据库
设置您的 RecyclerView
以观察本地存储,最好使用 Room 持久性库。这样,每当应用数据库中插入或修改数据时,这些更改都会自动反映在显示此数据的 RecyclerView
中。
网络和数据库
在开始观察数据库后,您可以使用 PagedList.BoundaryCallback
监听数据库何时用尽数据。然后,您可以从网络获取更多项目并将其插入到数据库中。如果您的 UI 正在观察数据库,那么您只需执行此操作。
处理网络错误
当使用网络获取或分页您使用 Paging 库显示的数据时,重要的是不要将网络始终视为“可用”或“不可用”,因为许多连接都是间歇性或不稳定的
- 特定服务器可能无法响应网络请求。
- 设备可能连接到速度慢或信号弱的网络。
相反,您的应用应该检查每个请求是否失败,并在网络不可用的情况下尽可能优雅地恢复。例如,您可以为用户提供一个“重试”按钮,如果数据刷新步骤不起作用,用户可以选择它。如果数据分页步骤中发生错误,最好自动重试分页请求。
更新您的现有应用
如果您的应用已从数据库或后端源使用数据,则可以直接升级到 Paging 库提供的功能。本节介绍了如何升级具有常见现有设计的应用。
自定义分页解决方案
如果您使用自定义功能从应用数据源加载数据的小子集,则可以用 PagedList
类中的逻辑替换此逻辑。PagedList
实例提供与常见数据源的内置连接。这些实例还为您可能包含在应用 UI 中的 RecyclerView
对象提供适配器。
使用列表而非页面加载的数据
如果使用内存中列表作为 UI 适配器的后端数据结构,并且列表中的项目数量可能很大,请考虑使用 PagedList
类观察数据更新。PagedList
实例可以使用 LiveData<PagedList>
或 Observable<List>
将数据更新传递到您的应用 UI,从而最大限度地减少加载时间和内存使用。更好的是,在您的应用中用 PagedList
对象替换 List
对象不需要对应用 UI 结构或数据更新逻辑进行任何更改。
使用 CursorAdapter 将数据游标与列表视图关联
您的应用可能会使用 CursorAdapter
将 Cursor
中的数据与 ListView
关联。在这种情况下,您通常需要将 ListView
迁移到 RecyclerView
作为应用列表 UI 容器,然后根据 Cursor
实例是否访问 SQLite 数据库,将 Cursor
组件替换为 Room 或 PositionalDataSource
。
在某些情况下,例如在使用 Spinner
实例时,您只提供适配器本身。然后,库会获取加载到该适配器中的数据并为您显示数据。在这些情况下,请将适配器的数据类型更改为 LiveData<PagedList>
,然后将此列表封装在 ArrayAdapter
对象中,然后再尝试让库类在 UI 中填充这些项目。
使用 AsyncListUtil 异步加载内容
如果您正在使用 AsyncListUtil
对象异步加载和显示信息组,Paging 库可让您更轻松地加载数据
- 您的数据不需要是位置性的。Paging 库允许您使用网络提供的键直接从后端加载数据。
- 您的数据可以是不可数的大量数据。使用 Paging 库,您可以将数据分页加载,直到没有剩余数据。
- 您可以更轻松地观察您的数据。Paging 库可以将您的应用 ViewModel 持有的数据以可观察的数据结构呈现。
数据库示例
以下代码段显示了所有组件协同工作的几种可能方式。
使用 LiveData 观察分页数据
以下代码段显示了所有组件协同工作。当数据库中添加、删除或更改音乐会事件时,RecyclerView
中的内容会自动高效地更新
Kotlin
@Dao interface ConcertDao { // The Int type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert> } class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData<PagedList<Concert>> = concertDao.concertsByDate().toLiveData(pageSize = 50) } class ConcertActivity : AppCompatActivity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact val viewModel: ConcertViewModel by viewModels() val recyclerView = findViewById(R.id.concert_list) val adapter = ConcertAdapter() viewModel.concertList.observe(this, PagedList(adapter::submitList)) recyclerView.setAdapter(adapter) } } class ConcertAdapter() : PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) { fun onBindViewHolder(holder: ConcertViewHolder, position: Int) { val concert: Concert? = getItem(position) // Note that "concert" is a placeholder if it's null. holder.bindTo(concert) } companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Concert>() { // Concert details may have changed if reloaded from the database, // but ID is fixed. override fun areItemsTheSame(oldConcert: Concert, newConcert: Concert) = oldConcert.id == newConcert.id override fun areContentsTheSame(oldConcert: Concert, newConcert: Concert) = oldConcert == newConcert } } }
Java
@Dao public interface ConcertDao { // The Integer type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") DataSource.Factory<Integer, Concert> concertsByDate(); } public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final LiveData<PagedList<Concert>> concertList; public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new LivePagedListBuilder<>( concertDao.concertsByDate(), /* page size */ 50).build(); } } public class ConcertActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ConcertViewModel viewModel = new ViewModelProvider(this).get(ConcertViewModel.class); RecyclerView recyclerView = findViewById(R.id.concert_list); ConcertAdapter adapter = new ConcertAdapter(); viewModel.concertList.observe(this, adapter::submitList); recyclerView.setAdapter(adapter); } } public class ConcertAdapter extends PagedListAdapter<Concert, ConcertViewHolder> { protected ConcertAdapter() { super(DIFF_CALLBACK); } @Override public void onBindViewHolder(@NonNull ConcertViewHolder holder, int position) { Concert concert = getItem(position); if (concert != null) { holder.bindTo(concert); } else { // Null defines a placeholder item - PagedListAdapter automatically // invalidates this row when the actual object is loaded from the // database. holder.clear(); } } private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK = new DiffUtil.ItemCallback<Concert>() { // Concert details may have changed if reloaded from the database, // but ID is fixed. @Override public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) { return oldConcert.getId() == newConcert.getId(); } @Override public boolean areContentsTheSame(Concert oldConcert, Concert newConcert) { return oldConcert.equals(newConcert); } }; }
使用 RxJava2 观察分页数据
如果您更喜欢使用 RxJava2 而不是 LiveData
,则可以创建 Observable
或 Flowable
对象
Kotlin
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: Observable<PagedList<Concert>> = concertDao.concertsByDate().toObservable(pageSize = 50) }
Java
public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final Observable<PagedList<Concert>> concertList; public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new RxPagedListBuilder<>( concertDao.concertsByDate(), /* page size */ 50) .buildObservable(); } }
然后,您可以使用以下代码段中的代码开始和停止观察数据
Kotlin
class ConcertActivity : AppCompatActivity() { private val adapter = ConcertAdapter() // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact private val viewModel: ConcertViewModel by viewModels() private val disposable = CompositeDisposable() public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val recyclerView = findViewById(R.id.concert_list) recyclerView.setAdapter(adapter) } override fun onStart() { super.onStart() disposable.add(viewModel.concertList .subscribe(adapter::submitList))) } override fun onStop() { super.onStop() disposable.clear() } }
Java
public class ConcertActivity extends AppCompatActivity { private ConcertAdapter adapter = new ConcertAdapter(); private ConcertViewModel viewModel; private CompositeDisposable disposable = new CompositeDisposable(); @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); RecyclerView recyclerView = findViewById(R.id.concert_list); viewModel = new ViewModelProvider(this).get(ConcertViewModel.class); recyclerView.setAdapter(adapter); } @Override protected void onStart() { super.onStart(); disposable.add(viewModel.concertList .subscribe(adapter.submitList(flowableList) )); } @Override protected void onStop() { super.onStop(); disposable.clear(); } }
ConcertDao
和 ConcertAdapter
的代码对于基于 RxJava2 的解决方案和基于 LiveData
的解决方案都是相同的。
提供反馈
通过这些资源与我们分享您的反馈和想法
- 问题跟踪器
- 报告问题,以便我们修复错误。
其他资源
要了解有关 Paging 库的更多信息,请查阅以下资源。
示例
Codelab
视频
为您推荐
- 注意:如果 JavaScript 关闭,将显示链接文本
- 迁移到 Paging 3
- 显示分页列表
- 收集分页数据