Paging
概述
Jetpack 的一个分页库,帮助开发者更好的分离ui和数据获取的逻辑,降低项目的耦合。本文主要描述从服务器直接获取数据
库架构
- DataSource.Factory, 顾名思义,就是数据源工厂。抽象类,需要我们实现create() 方法,返回DataSource 对象。**注意:create() 每次被调用都应该返回新对象,**不然调用invalidate() 时不仅不能刷新列表还会出现死循环。(原因后面再解释)
fun createDataSourceFactory(dataSource: DataSource<Long, UserBean>):DataSource.Factory<Long, UserBean>{
return object: DataSource.Factory<Long,UserBean>(){
override fun create(): DataSource<Long, UserBean> {
return dataSource
}
}
}
-
DataSource, Paging已经帮我们提供了三个非常全面的实现,分别是:
- PageKeyedDataSource: 通过当前页相关的key来获取数据,非常常见的是key作为请求的page的大小。
- ItemKeyedDataSource: 通过具体item数据作为key,来获取下一页数据。例如聊天会话,请求下一页数据可能需要上一条数据的id。
- PositionalDataSource: 通过在数据中的position作为key,来获取下一页数据。这个典型的就是上面所说的在Database中的运用。
这里我们只介绍一下 ItemKeyedDataSource 的使用。 ItemKeyedDataSource 同样是一个抽象类,需要我们实现以下四个方法:
- loadInitial(LoadInitialParams params,LoadInitialCallback callback) 加载初始(第一页)数据
- loadAfter(LoadParams params,LoadCallback callback) 加载下一页数据
- loadBefore(LoadParams params,LoadCallback callback) 加载上一页数据,一般用在新闻资讯类列表,把请求的数据加载到列表前面。不需要就空着
- Key getKey(Value item)
-
PagedList, 分页库的关键组件是 PagedList 类,用于加载应用数据块或页面。随着所需数据的增多,系统会将其分页到现有的 PagedList 对象中。如果任何已加载的数据发生更改,会从 LiveData 或基于 RxJava2 的对象向可观察数据存储器发出一个新的 PagedList 实例。随着 PagedList 对象的生成,应用界面会呈现其内容,同时还会考虑界面控件的生命周期。(官方翻译) 可以通过 LivePagedListBuilder 创建
val userLiveData =
LivePagedListBuilder(mRepository.createDataSourceFactory(createDataSource()),
mRepository.createConfig())
.setInitialLoadKey(1)
.build()
LivePagedListBuilder(DataSource.Factory<Key, Value> dataSourceFactory, PagedList.Config config)
LivePagedListBuilder 需要DataSource.Factory(上面介绍了)、PagedList.Config(提供分页需要的参数)
Config(
int pageSize, //一页加载多少数据
int prefetchDistance,//加载到第几条时请求下一页数据
boolean enablePlaceholders, 是否使用占位符
int initialLoadSizeHint, //第一次加载多少数据
int maxSize //最多保存多少数据
){...}
- PagedListAdapter, 与RecyclerView.Adapter的使用区别不大,只是对getItemCount与getItem进行了重写,因为它使用到了DiffUtil,避免对数据的无用更新。
class PagingAdapter : PagedListAdapter<ArticleModel, PagingVH>(diffCallbacks) {
companion object {
private val diffCallbacks = object : DiffUtil.ItemCallback<ArticleModel>() {
override fun areItemsTheSame(oldItem: ArticleModel, newItem: ArticleModel): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: ArticleModel, newItem: ArticleModel): Boolean = oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagingVH = PagingVH(R.layout.item_paging_article_layout, parent)
override fun onBindViewHolder(holder: PagingVH, position: Int) = holder.bind(getItem(position))
}
这样adapter也已经构建完成,最后一旦PagedList被观察到,使用submitList传入到adapter即可。
viewModel.userList.observe(this, Observer {
adapter.submitList(it)
})
详细使用可以看看我的Demo,使用了mvvm + LiveData + Koin + 协程 + Retrofit。项目地址在最上面
问题
- 为什么 DataSource.Factory 的create() 每次调用需要返回新对象
因为当 invalidate() 调用时 DataSource 的 mInvalid 会被设置为 true,然后调用onInvalidated()
//源码 @AnyThread public void invalidate() { if (mInvalid.compareAndSet(false, true)) { for (InvalidatedCallback callback : mOnInvalidatedCallbacks) { callback.onInvalidated(); } } }
LivePagedListBuilder 的create() 重写了 DataSource.InvalidatedCallback
//LivePagedListBuilder 源码
@AnyThread
@NonNull
@SuppressLint("RestrictedApi")
private static <Key, Value> LiveData<PagedList<Value>> create(
@Nullable final Key initialLoadKey,
@NonNull final PagedList.Config config,
@Nullable final PagedList.BoundaryCallback boundaryCallback,
@NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
@NonNull final Executor notifyExecutor,
@NonNull final Executor fetchExecutor) {
return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
@Nullable
private PagedList<Value> mList;
@Nullable
private DataSource<Key, Value> mDataSource;
private final DataSource.InvalidatedCallback mCallback =
new DataSource.InvalidatedCallback() {
@Override
public void onInvalidated() {
invalidate();
}
};
@SuppressWarnings("unchecked") // for casting getLastKey to Key
@Override
protected PagedList<Value> compute() {
@Nullable Key initializeKey = initialLoadKey;
if (mList != null) {
initializeKey = (Key) mList.getLastKey();
}
do {
if (mDataSource != null) {
mDataSource.removeInvalidatedCallback(mCallback);
}
mDataSource = dataSourceFactory.create();
mDataSource.addInvalidatedCallback(mCallback);
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
} while (mList.isDetached());
return mList;
}
}.getLiveData();
}
}
可以看到当 mList.isDetached() = true 时就会进入死循环。它的默认值是为false 的。在do 里面 mDataSource = dataSourceFactory.create(); mDataSource是重新获取了的,然后通过 PagedList.Builder 去获取新的 mList 数据。
@WorkerThread
TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<T> boundaryCallback,
@NonNull Config config,
int position) {
super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
final int pageSize = mConfig.pageSize;
mLastLoad = position;
if (mDataSource.isInvalid()) {
detach();
} else {
final int firstLoadSize =
(Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize;
final int idealStart = position - firstLoadSize / 2;
final int roundedPageStart = Math.max(0, idealStart / pageSize * pageSize);
mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize,
pageSize, mMainThreadExecutor, mReceiver);
}
}
获取新数据前是会判断 mDataSource.isInvalid() ,这时如果我们重新的DataSource的create() 的方法返回的不是新对象,而是之前的对象,这个mDataSource已经调用invalidate() 因此就会走到 detach() 方法,不获取新数据。
//源码
@SuppressWarnings("WeakerAccess")
public void detach() {
mDetached.set(true);
}
可以看到 detach() 会把mDetached 这位true,这就会导致mList.isDetached() 获取的时候是true,LivePagedListBuilder 的 create() 方法就会死循环。