1.概念
Paging 库可帮助您加载和显示来自本地存储或网络中更大的数据集中的数据页面
。此方法可让您的应用更高效地利用网络带宽和系统资源。
- 分页数据的内存中缓存。该功能可确保您的应用在处理分页数据时高效利用系统资源。
- 内置的请求重复信息删除功能,可确保您的应用高效利用网络带宽和系统资源。
- 可配置的 RecyclerView 适配器,会在用户滚动到已加载数据的末尾时自动请求数据。
- 对 Kotlin 协程和 Flow 以及 LiveData 和 RxJava 的一流支持。
- 内置对错误处理功能的支持,包括刷新和重试功能。
val paging_version = “3.0.0”
implementation(“androidx.paging:paging-runtime:$paging_version”)
2.库的架构
Paging 库直接集成到推荐的 Android 应用架构中。该库的组件在应用的三个层运行:
- 代码库层
- ViewModel 层
- 界面层
2.1 代码库层
代码库层中的主要 Paging 库组件是 PagingSource
。每个 PagingSource 对象都定义了数据源,以及如何从该数据源检索数据。PagingSource 对象可以从任何单个数据源(包括网络来源和本地数据库)加载数据
。
您可能使用的另一个 Paging 库组件是 RemoteMediator。RemoteMediator 对象会处理来自分层数据源(例如具有本地数据库缓存的网络数据源)
的分页。
2.2 ViewModel 层
Pager 组件提供了一个公共 API,基于 PagingSource 对象和 PagingConfig 配置对象来构造在响应式流中公开的 PagingData 实例
。
将 ViewModel 层连接到界面的组件是 PagingData。PagingData 对象是用于存放分页数据快照的容器。它会查询 PagingSource 对象并存储结果。
2.3 界面层
界面层中的主要 Paging 库组件是 PagingDataAdapter,它是一种处理分页数据的 RecyclerView 适配器。
此外,您也可以使用随附的 AsyncPagingDataDiffer 组件来构建自己的自定义适配器
3.流程
3.1 定义数据源
第一步是定义用于标识数据源的 PagingSource 实现。PagingSource API 类包含 load() 方法,您必须替换该方法
,以指明如何从相应数据源检索分页数据。
直接使用 PagingSource 类即可通过 Kotlin 协程进行异步加载。Paging 库还提供了支持其他异步框架的类:
如需使用 RxJava,请改为实现 RxPagingSource
。
如需使用 Guava 中的 ListenableFuture,请改为ListenableFuturePagingSource。
选择键和值类型
PagingSource<Key, Value> 有两种类型参数:Key 和 Value。
- 键定义了用于加载数据的标识符,
- 值是数据本身的类型。 列表条目的数据对象类型
例如,如果您通过将 Int 页码传递给 Retrofit 来从网络加载各页 User 对象,应选择 Int 作为 Key 类型,选择 User 作为 Value 类型。
定义 PagingSource
实现了按页码加载各页对象的 PagingSource。
Key 类型为 Int,页码
Value 类型为 User。 列表中每项数据类型
class ExamplePagingSource extends RxPagingSource<Integer, User> {
@NonNull
private ExampleBackendService mBackend;
@NonNull
private String mQuery;
ExamplePagingSource(@NonNull ExampleBackendService backend,
@NonNull String query) {
mBackend = backend;
mQuery = query;
}
@NotNull
@Override
public Single<LoadResult<Integer, User>> loadSingle(
@NotNull LoadParams<Integer> params) {
// Start refresh at page 1 if undefined.
Integer nextPageNumber = params.getKey();
if (nextPageNumber == null) {
nextPageNumber = 1;
}
return mBackend.searchUsers(mQuery, nextPageNumber)
.subscribeOn(Schedulers.io())
.map(this::toLoadResult)
.onErrorReturn(LoadResult.Error::new);
}
private LoadResult<Integer, User> toLoadResult(
@NonNull SearchUserResponse response) {
return new LoadResult.Page<>(
response.getUsers(),
null, // Only paging forward.
response.getNextPageNumber(),
LoadResult.Page.COUNT_UNDEFINED,
LoadResult.Page.COUNT_UNDEFINED);
}
//该方法接受 PagingState 对象作为参数,并且当数据在初始加载后刷新或失效时,该方法会返回要传递给 load() 方法的键。在后续刷新数据时,Paging 库会自动调用此方法。
@Nullable
@Override
public Integer getRefreshKey(@NotNull PagingState<Integer, User> state) {
// Try to find the page key of the closest page to anchorPosition, from
// either the prevKey or the nextKey, but you need to handle nullability
// here:
// * prevKey == null -> anchorPage is the first page.
// * nextKey == null -> anchorPage is the last page.
// * both prevKey and nextKey null -> anchorPage is the initial page, so
// just return null.
Integer anchorPosition = state.getAnchorPosition();
if (anchorPosition == null) {
return null;
}
LoadResult.Page<Integer, User> anchorPage = state.closestPageToPosition(anchorPosition);
if (anchorPage == null) {
return null;
}
Integer prevKey = anchorPage.getPrevKey();
if (prevKey != null) {
return prevKey + 1;
}
Integer nextKey = anchorPage.getNextKey();
if (nextKey != null) {
return nextKey - 1;
}
return null;
}
}
3.2 处理错误 —待补充
数据加载请求可能因多种原因而失败,特别是在通过网络加载时。通过从 load() 方法返回 LoadResult.Error 对象,可报告在加载过程中遇到的错误.
return backend.searchUsers(searchTerm, nextPageNumber)
.subscribeOn(Schedulers.io())
.map(this::toLoadResult)
.onErrorReturn(LoadResult.Error::new);
3.4 设置 PagingData 流
接下来,您需要来自 PagingSource 实现的分页数据流。通常,您应在 ViewModel 中设置数据流。Pager 类提供的方法可显示来自 PagingSource 的 PagingData 对象的响应式流
。Paging 库支持使用多种流类型,包括 Flow、LiveData 以及 RxJava 中的 Flowable 和 Observable 类型。
当您创建 Pager 实例来设置响应式流时,必须为实例提供 PagingConfig 配置对象和告知 Pager 如何获取 PagingSource 实现实例的函数:
// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact.
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel);
Pager<Integer, User> pager = Pager<>(
new PagingConfig(/* pageSize = */ 20),
() -> ExamplePagingSource(backend, query));
Flowable<PagingData<User>> flowable = PagingRx.getFlowable(pager);
PagingRx.cachedIn(flowable, viewModelScope);
-
cachedIn() 运算符使数据流可共享,并使用提供的 CoroutineScope 缓存加载的数据。此示例使用 Lifecycle lifecycle-viewmodel-ktx 工件提供的 viewModelScope。
-
Pager 对象会调用 PagingSource 对象的 load() 方法,为其提供 LoadParams 对象,并接收 LoadResult 对象作为交换。
3.5 定义 RecyclerView 适配器
类似recycler.Adapter的方式,泛型参数为:
1.列表的每项item的数据类型
2.自定义的viewHolder
同时还有个DiffUtil.ItemCallBack回调,主要用来比对数据变化,从而决定更新对应UI;并执行条目动画。
public class MyAdapter extends PagingDataAdapter<MotoBean.Datam, MyAdapter.ViewHolder> {
Context context;
public MyAdapter( Context context) {
super(new DiffUtil.ItemCallback<MotoBean.Datam>(){
@Override
public boolean areItemsTheSame(@NonNull @NotNull MotoBean.Datam oldItem, @NonNull @NotNull MotoBean.Datam newItem) {
return oldItem.getGoodId()==newItem.getGoodId();
}
@Override
public boolean areContentsTheSame(@NonNull @NotNull MotoBean.Datam oldItem, @NonNull @NotNull MotoBean.Datam newItem) {
return oldItem.getGoodId()==newItem.getGoodId();
}
});
this.context = context;
}
@NonNull
@NotNull
@Override
public ViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.moto_item,parent,false));
}
@Override
public void onBindViewHolder(@NonNull @NotNull ViewHolder holder, int position) {
MotoBean.Datam item = getItem(position);
holder.title.setText(item.getGoodName()+" "+item.getBrandName());
Glide.with(context).load(item.getGoodPic()).into(holder.img);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView img;
TextView title;
public ViewHolder(@NonNull @NotNull View itemView) {
super(itemView);
img = itemView.findViewById(R.id.imageView);
title = itemView.findViewById(R.id.textView);
}
}
}
3.6 在界面中显示分页数据
在 Activity 的 onCreate 或 Fragment 的 onViewCreated 方法中执行以下步骤:
创建 PagingDataAdapter 类的实例。
将 PagingDataAdapter 实例传递给您要显示分页数据的 RecyclerView 列表。
观察 PagingData 流,并将生成的每个值传递给适配器的 submitData() 方法
motoViewModel.getMotoBeanMutableLiveData().observe(this,datamPagingData -> {
myAdapter.submitData(getLifecycle(),datamPagingData);
});
注意
:submitData() 方法会挂起,并且直到 PagingSource 失效或调用适配器的刷新方法后才会返回。这意味着,submitData() 调用之后的代码的执行时间可能会显著晚于预期
代码参考:https://github.com/cts33/LLwanAndroid_java