如果你还没用过Paging的话,很正常,毕竟现在用的并不多,做为android的开发人员分页的做法我们习惯写一个自定义控件,下拉的时候刷新(请求接口的页数置为1),上拉加载更多(请求接口数据页数实现累加)。然而,一种新的框架出来,我们应该尽可能的去了解它的架构,吸取人家谷歌工程师的代码精华。
好了,现在咱们先来了解一下怎么样用用这个分页库:
第一步:引入分页库
implementation "android.arch.paging:runtime:2.1.0"
既然是分页那么肯定用RecyclerView,那么
第二步:为RecyclerView设置适配器,实现适配器PagedListAdapter这个类,也就是我们常用的适配器的子类。(用法和普通适配器的用法一样)。
第三步:PagedListAdapter实现DiffUtil.ItemCallback,这个类为判断数据集合是否一样提供了分析,没用过DiffUtil的小伙伴请自行谷歌一下它的用法。
第四步:用LivePagedListBuilder类构建LiveData类(让数据和Activity或Frament的生命周期绑定)不了解的小伙伴可以看我的这篇博客https://blog.csdn.net/xiatiandefeiyu/article/details/78663612。
PagedList.Config config = new Builder()
.setInitialLoadSizeHint(PAGE_SIZE)
.setPageSize(PAGE_SIZE)
.build();
products = new LivePagedListBuilder(dataSourceFactory, config)
.setInitialLoadKey(1).setFetchExecutor(AppsExecutor.networkIO())
.build();
return products;
第5步:实现DataSourceFactory工厂类创建加载数据的的DataSource(实现这个类就是为了加载数据,不管是从网络获取,还是从数据库、缓存、sd卡等获取)
public class ProductDataFactory extends DataSource.Factory {
public MutableLiveData datasourceLiveData = new MutableLiveData<>();
@Override
public DataSource create() {
ProductDataSource dataSource = new ProductDataSource(Service.get());
datasourceLiveData.postValue(dataSource);
return dataSource;
}
}
DataSource的实现类有ListDataSource、TiledDataSource、WrapperPositionalDataSource、LimitOffsetDataSource、PositionalDataSource、ItemKeyedDataSource、PageKeyedDataSource等你可以根据业务需要自己实现不同的类。我这里使用的是PageKeyedDataSource
第6步:实现LiveData的的oberve的方法注册观察者回调
mProductsViewModel.getProducts().observe(this, pagedList -> {
mAdapter.submitList(pagedList);
第7步:在适配器中的onBindViewHolder方法中调用getItem(调用它就可以实现加载更多了)
ok,现在来分析源码是怎么进行的,首先明确几个重要类PagedListAdapter(负责适配数据,负责在getItem方法中实现继续加载),pageList类(持有数据的类),DataSource类(负责加载数据),LiveData(负责生命周期数据变化回调)。
咱们先从注册观察者跟进代码:
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) {
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && existing.owner != wrapper.owner) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
这里的getLifecycle得到的是 LifecycleRegistry类,进入:
public void addObserver(@NonNull LifecycleObserver observer) {
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
if (previous != null) {
return;
}
LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
if (lifecycleOwner == null) {
// it is null we should be destroyed. Fallback quickly
return;
}
boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
State targetState = calculateTargetState(observer);
mAddingObserverCounter++;
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
}
if (!isReentrance) {
// we do sync only on the top level.
sync();
}
mAddingObserverCounter--;
}
这段代码最重要的是statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState))这句话,添加观察者的时候,这个方法肯定会调用一次,最终会调用LifecycleBoundObserver的的onStateChanged方法
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
然后调用到父类的activeStateChanged的方法
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
if (mActive) {
dispatchingValue(this);
}
}
}
终于到重点了,看第9行的判断,第一次调用的时候肯定为true,然后会调用onActive的方法,废话了这么多,总结起来就是LiveData的observe方法调用的时候会执行一次LiveData的onActive方法,那么这个LiveData在哪创建的,现在回到第四步,LiveData通过LivePagedListBuilder创建的,来看一下它的build方法
public LiveData> build() {
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
}
private static LiveData> create(
@Nullable final Key initialLoadKey,
@NonNull final PagedList.Config config,
@Nullable final PagedList.BoundaryCallback boundaryCallback,
@NonNull final DataSource.Factory dataSourceFactory,
@NonNull final Executor notifyExecutor,
@NonNull final Executor fetchExecutor) {
return new ComputableLiveData>(fetchExecutor) {
@Nullable
private PagedList mList;
@Nullable
private DataSource mDataSource;
private final DataSource.InvalidatedCallback mCallback =
new DataSource.InvalidatedCallback() {
@Override
public void onInvalidated() {
invalidate();
}
};
@Override
protected PagedList compute() {
@Nullable Key initializeKey = initialLoadKey;
if (mList != null) {
//noinspection unchecked
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();
}
最终通过ComputableLiveData的getLiveData创建了LiveData,接着来看getLiveData方法,LiveData是在ComputableLiveData的构造方法中创建的
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData() {
@Override
protected void onActive() {
mExecutor.execute(mRefreshRunnable);
}
};
}
可以看到onActive方法在线程中执行了这面这段代码
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) {
mLiveData.postValue(value);
}
} finally {
// release compute lock
mComputing.set(false);
}
}
// check invalid after releasing compute lock to avoid the following scenario.
// Thread A runs compute()
// Thread A checks invalid, it is false
// Main thread sets invalid to true
// Thread B runs, fails to acquire compute lock and skips
// Thread A releases compute lock
// We've left invalid in set state. The check below recovers.
} while (computed && mInvalid.get());
}
};
它首先调用了自己的compute方法,如下:
@Nullable Key initializeKey = initialLoadKey;
if (mList != null) {
//noinspection unchecked
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;
}
compute方法用来创建加载器Datasource以及数据源PageList(PagedListAdapter通过它获取数据),接着再看一下PageList的build方法
public PagedList build() {
// TODO: define defaults, once they can be used in module without android dependency
if (mNotifyExecutor == null) {
throw new IllegalArgumentException("MainThreadExecutor required");
}
if (mFetchExecutor == null) {
throw new IllegalArgumentException("BackgroundThreadExecutor required");
}
//noinspection unchecked
return PagedList.create(
mDataSource,
mNotifyExecutor,
mFetchExecutor,
mBoundaryCallback,
mConfig,
mInitialKey);
}
}
/**
最终创建ContiguousPagedList
ContiguousPagedList(
@NonNull ContiguousDataSource dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback boundaryCallback,
@NonNull Config config,
final @Nullable K key,
int lastLoad) {
super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
mLastLoad = lastLoad;
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
}
}
它的构造方法的倒数第三行里面实现了Datasource的的加载loadInitial方法在第一次加载数据(只有调用一次loadInitial,isInvalid()才返回true)。
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback callback)
这个方法的最后一个参数用来将你加载的数据结果返回给DataSource,并最终PageList进行数据保存
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver receiver) {
LoadInitialCallbackImpl callback =
new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
loadInitial(new LoadInitialParams(initialLoadSize, enablePlaceholders), callback);
// If initialLoad's callback is not called within the body, we force any following calls
// to post to the UI thread. This constructor may be run on a background thread, but
// after constructor, mutation must happen on UI thread.
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}
可以看到这个回调参数就是LoadInitialCallbackImpl类:
public void onResult(@NonNull List data, @Nullable Key previousPageKey,
@Nullable Key nextPageKey) {
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
mDataSource.initKeys(previousPageKey, nextPageKey);
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
}
}
这里又通过mCallbackHelper回调出去:
void dispatchResultToReceiver(final @NonNull PageResult result) {
Executor executor;
synchronized (mSignalLock) {
if (mHasSignalled) {
throw new IllegalStateException(
"callback.onResult already called, cannot call again.");
}
mHasSignalled = true;
executor = mPostExecutor;
}
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
mReceiver.onPageResult(mResultType, result);
}
});
} else {
mReceiver.onPageResult(mResultType, result);
}
}
}
下面又通过 PageResult.Receiver
@NonNull PageResult pageResult) {
if (pageResult.isInvalid()) {
detach();
return;
}
if (isDetached()) {
// No op, have detached
return;
}
List 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 (resultType == PageResult.APPEND) {
mStorage.appendPage(page, ContiguousPagedList.this);
} else if (resultType == PageResult.PREPEND) {
mStorage.prependPage(page, ContiguousPagedList.this);
} else {
throw new IllegalArgumentException("unexpected resultType " + resultType);
}
//省略若干行......
}
}
};
这里做的工作只是判断是第一次加载,还是第二次存放数据(append),然后将数据保存到PagedStorage这个类中,现在再回到前面的compute中,它返回的T是PageList,而此时的PageList已通过DataSource加载填充了数据,然后又调用了 LiveData的postValue(value)方法,这个方法会触发(在activitty、frament处于生命周期活跃状态下)观察者的回调告诉观察者,我得数据改变了。你需要做点什么,也就是下面代码
mProductsViewModel.getProducts().observe(this, new Observer>() {
@Override
public void onChanged(@Nullable PagedList products) {
mAdapter.submitList(products);
}
});
的onChanged方法会被回调,这里就做了一件事,向适配器中赋值PageList,然后适配器通过PageList获取数据,从而RecyclerView有了第一页的数据,当然这里源码还有DiffUtil做了数据的分析,从而提高数据刷新速度。这就是第一页数据怎么加载出来的,那么后面数据怎么触发来的呢?
上面说过它是通过getItem数据来每次加载更多的,那么来看一下它的代码
protected T getItem(int position) {
return mDiffer.getItem(position);
}
继续跟入
public T getItem(int index) {
if (mPagedList == null) {
if (mSnapshot == null) {
throw new IndexOutOfBoundsException(
"Item count is zero, getItem() call is invalid");
} else {
return mSnapshot.get(index);
}
}
mPagedList.loadAround(index);
return mPagedList.get(index);
}
看到了什么,调用了pageList的loadAround方法,肯定是去加载数据吗。来看一下
public void loadAround(int index) {
mLastLoad = index + getPositionOffset();
loadAroundInternal(index);
mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
/*
* mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
* dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
* and accesses happen near the boundaries.
*
* Note: we post here, since RecyclerView may want to add items in response, and this
* call occurs in PagedListAdapter bind.
*/
tryDispatchBoundaryCallbacks(true);
}
这里又调用了loadAroundInternal的方法,最终调用ContiguousPagedList类的loadAroundInternal方法
protected void loadAroundInternal(int index) {
int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
int appendItems = index + mConfig.prefetchDistance
- (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0) {
schedulePrepend();
}
mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
if (mAppendItemsRequested > 0) {
scheduleAppend();
}
}
传过来的参数是当前RecyclerView的item的position,其中prefetchDistance为预加载的数量,mStorage.getLeadingNullCount()默认为0,从这个方法代码可以推测出如果当前的预加载减掉索引如果大于0的话,会调用DataSource的loadBefore方法,如果当前的预加载数量+索引-目前已经加载的数量的话>0的话调用DataSource的loadAfter方法,从而实现数据的分页加载,所以我们只要实现loadBefore或loadAfter一个方法就好了,最后是添加的话就会回调
public void onPageAppended(int endPosition, int changedCount, int addedCount) {
// consider whether to post more work, now that a page is fully appended
mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
mAppendWorkerRunning = false;
if (mAppendItemsRequested > 0) {
// not done appending, keep going
scheduleAppend();
}
// finally dispatch callbacks, after append may have already been scheduled
notifyChanged(endPosition, changedCount);
notifyInserted(endPosition + changedCount, addedCount);
}
方法来通知适配器数据改变了。