赞
踩
Paging主要由三个部分组成:DataSource PageList PageListAdapter。
DataSource<Key, Value>从字面意思理解是一个数据源,其中key对应加载数据的条件信息,Value对应加载数据的实体类。
DataSource是一个抽象类,但是我们不能直接继承它实现它的子类。但是Paging库里提供了它的三个子类供我们继承用于不同场景的实现。
PagedList配置如下
private PagedList.Config config = new PagedList.Config.Builder()
.setInitialLoadSizeHint(10) // 配置 初始化 加载条数
.setPageSize(10) // 设置 每页 加载条数
.setPrefetchDistance(2)
.setMaxSize(20)
.setEnablePlaceholders(false)
.build();
private LiveData<PagedList<Cheese>> livePagedList = new LivePagedListBuilder<Integer,Cheese>(factory,config)
.setInitialLoadKey(0)// 指定初始化加载的key
// .setFetchExecutor()// 指定线程执行异步操作
.setBoundaryCallback(new CheeseBoundaryCallback())
.build();
场景描述:
数据库默认有100条数据。初始化加载0~10条数据,PagedList最多存储20条数据。在滑动在距离上边缘2条和下边缘2条的时候加载更多数据。
运行现象:
滑动过程中容易出现只能加载20条数据,超过20条的数据无法加载。
private PagedList.Config config = new PagedList.Config.Builder()
.setInitialLoadSizeHint(10) // 配置 初始化 加载条数
.setPageSize(10) // 设置 每页 加载条数
.setPrefetchDistance(2)
.setMaxSize(10 *3) // 配置pagedList最大加载条数为3倍的PageSize
.setEnablePlaceholders(false)
.build();
关于解决方案的思路是:思路是保证loadBefore,loadInitial,loadAfter三个回调获取到的数据都可以完整的保存在PageList里面。
由于遇到了以上的问题,因此,对Paging如果更新RecyclerView,已经实现加载更多的流程有了兴趣,因此,对代码分析如下。
使用PagedList一般是将PagedList存储在ViewHolder中,因此ViewHolder的代码一般如下
public class CheeseViewModel extends ViewModel{
private LiveData<PagedList<Cheese>> livePagedList = new LivePagedListBuilder<Integer,Cheese>(factory,config)
.setInitialLoadKey(0)// 指定初始化加载的key
// .setFetchExecutor()// 指定线程执行异步操作
.setBoundaryCallback(new CheeseBoundaryCallback())
.build();
}
根据源码可以得知,最终调用的代码是
static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource, @NonNull Executor notifyExecutor, @NonNull Executor fetchExecutor, @Nullable BoundaryCallback<T> boundaryCallback, @NonNull Config config, @Nullable K key){ ... if (dataSource.isContiguous() || !config.enablePlaceholders){ return new ContiguousPagedList<>(contigDataSource, notifyExecutor, fetchExecutor, boundaryCallback, config, key, lastLoad); } }
在ContiguousPagedList构造函数中调用dispatchLoadInitial方法。
dispatchLoadInitial 即回调到ItemKeyedDataSource的 dispatchLoadInitial 函数中
public abstract class ItemKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value>{
@Override
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
LoadInitialCallbackImpl<Value> callback =
new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
// 注意 loadInitial 是抽象方法,需要在子类实现
loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}
}
抽象方法在继承ItemKeyedDataSource的子类实现,源码如下
public class MyItemKeyedDataSource extends ItemKeyedDataSource<Integer,Cheese>{
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback callback) {
...
// 获取cheeseList,通过回调传递给ItemKeyedDataSource
callback.onResult(cheeseList);
}
}
LoadInitialCallback 在ItemKeyedDataSource的实现如下
public abstract class ItemKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value>{
static class LoadInitialCallbackImpl<Value> extends LoadInitialCallback<Value> {
public void onResult(@NonNull List<Value> data){
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
// 调用核心的Receiver
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
}
}
}
}
核心的ContiguousPagedList#mReceiver如下,PageResult.Receiver实现RecyclerView item的初始化已经加载更新。
class ContiguousPagedList<K, V> extends PagedList<V> implements Callback{ PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() { // Creation thread for initial synchronous load, otherwise main thread // Safe to access main thread only state - no other thread has reference during construction @AnyThread @Override public void onPageResult(@PageResult.ResultType int resultType, @NonNull PageResult<V> pageResult) { if (pageResult.isInvalid()) { detach(); return; } if (isDetached()) { // No op, have detached return; } List<V> page = pageResult.page; if (resultType == PageResult.INIT) { mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls, pageResult.positionOffset, ContiguousPagedList.this); if (mLastLoad == LAST_LOAD_UNSPECIFIED) { // Because the ContiguousPagedList wasn't initialized with a last load position, // initialize it to the middle of the initial load mLastLoad = pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2; } } else { // if we end up trimming, we trim from side that's furthest from most recent access boolean trimFromFront = mLastLoad > mStorage.getMiddleOfLoadedRange(); // is the new page big enough to warrant pre-trimming (i.e. dropping) it? boolean skipNewPage = mShouldTrim && mStorage.shouldPreTrimNewPage( mConfig.maxSize, mRequiredRemainder, page.size()); if (resultType == PageResult.APPEND) { if (skipNewPage && !trimFromFront) { // don't append this data, drop it mAppendItemsRequested = 0; mAppendWorkerState = READY_TO_FETCH; } else { mStorage.appendPage(page, ContiguousPagedList.this); } } else if (resultType == PageResult.PREPEND) { if (skipNewPage && trimFromFront) { // don't append this data, drop it mPrependItemsRequested = 0; mPrependWorkerState = READY_TO_FETCH; } else { mStorage.prependPage(page, ContiguousPagedList.this); } } else { throw new IllegalArgumentException("unexpected resultType " + resultType); } if (mShouldTrim) { if (trimFromFront) { if (mPrependWorkerState != FETCHING) { if (mStorage.trimFromFront( mReplacePagesWithNulls, mConfig.maxSize, mRequiredRemainder, ContiguousPagedList.this)) { // trimmed from front, ensure we can fetch in that dir mPrependWorkerState = READY_TO_FETCH; } } } else { if (mAppendWorkerState != FETCHING) { if (mStorage.trimFromEnd( mReplacePagesWithNulls, mConfig.maxSize, mRequiredRemainder, ContiguousPagedList.this)) { mAppendWorkerState = READY_TO_FETCH; } } } } } if (mBoundaryCallback != null) { boolean deferEmpty = mStorage.size() == 0; boolean deferBegin = !deferEmpty && resultType == PageResult.PREPEND && pageResult.page.size() == 0; boolean deferEnd = !deferEmpty && resultType == PageResult.APPEND && pageResult.page.size() == 0; deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd); } } }; }
由于是初始化,因此resultType == PageResult.INIT
执行的分支并未调用postValue接口,更新数据,只是将数据保存在agedStorage mStorage 中。
if (resultType == PageResult.INIT) {
mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
pageResult.positionOffset, ContiguousPagedList.this);
if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
// Because the ContiguousPagedList wasn't initialized with a last load position,
// initialize it to the middle of the initial load
mLastLoad =
pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
}
}
这时候需要查看源码LivePagedListBuilder,ViewHolder持有的LiveData本质是ComputableLiveData,在第一次订阅观察者是,会调用onActive
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData<T>() {
@Override
protected void onActive() {
mExecutor.execute(mRefreshRunnable);
}
};
}
onActive执行mRefreshRunnable线程,
final Runnable mRefreshRunnable = new Runnable() { @WorkerThread @Override public void run() { boolean computed; do { computed = false; // compute can happen only in 1 thread but no reason to lock others. if (mComputing.compareAndSet(false, true)) { // as long as it is invalid, keep computing. try { T value = null; while (mInvalid.compareAndSet(true, false)) { computed = true; value = compute(); } if (computed) { // 这里调用postValue接口,这时候在生命周期允许的条件下回调更新UI mLiveData.postValue(value); } } finally { mComputing.set(false); } } scenario. } while (computed && mInvalid.get()); } };
Activity或者Fragment中注册PagedList的观察者代码如下
viewModel.getLivePagedList().observe(getViewLifecycleOwner(),
cheeses ->{
Log.d(TAG,"===submitList==="+cheeses.size());
// 这里最终调用adapter的更新接口,更新UI
adapter.submitList(cheeses);
}
);
以上即为使用PagedListAdapter初始化RecyclerView的流程。
RecyclerView在创建Item过程中,会调用onCreateViewHolder和onBindViewHolder等接口,在onBindViewHolder接口中,需要获取Adapter的Item列表,用于与item界面元素绑定。
public class CheeseAdapter extends PagedListAdapter<Cheese,CheeseViewHolder> {
@Override
public void onBindViewHolder(@NonNull CheeseViewHolder holder, int position) {
holder.bindTo(getItem(position));
}
}
getItem最终调用到
public class AsyncPagedListDiffer{
public T getItem(int index) {
…
// 这个方法实现了滑动刷新
mPagedList.loadAround(index);
return mPagedList.get(index);
}
}
protected void loadAroundInternal(int index) { int prependItems = getPrependItemsRequested(mConfig.prefetchDistance, index, mStorage.getLeadingNullCount()); int appendItems = getAppendItemsRequested(mConfig.prefetchDistance, index, mStorage.getLeadingNullCount() + mStorage.getStorageCount()); mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested); if (mPrependItemsRequested > 0) { // 调用 loadBefore schedulePrepend(); } mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested); if (mAppendItemsRequested > 0) { // loadAfter scheduleAppend(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。