RecyclerView使用paging就是多了对数据的拉取,使得RecyclerView的数据和显示更加的解耦,RecyclerView对paging的使用多了如下几步:
instance = CustomAdapter.getInstance(this);
factory = new CustomPageDataSourceFactory<>();
build = new LivePagedListBuilder<Integer, String>(factory,
new PagedList.Config.Builder().setPageSize(20).setInitialLoadSizeHint(20)
.setPrefetchDistance(3).build()).setInitialLoadKey(4).build();
build.observe(this, it -> instance.submitList(it));
也就是将数据设置到adapter中,看到这,我们该想paging的源码该从哪里入手呢?想想就应该知道应该是从LivePagedListBuilder入手,好的,那就从LivePagedListBuilder的build()方法中去看看:
LivePagedListBuilder的build()方法:
public LiveData<PagedList<Value>> build() {
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
}
就是简单调用了它的create()方法,那就看下它的create()方法:
private static <Key, Value> LiveData<PagedList<Value>> create(
@Nullable final Key initialLoadKey,//ItemKeyedDataSource会用到
@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();
}
};
@Override
protected PagedList<Value> 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()方法,接下来就去看下ComputableLiveData的构造方法:
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData<T>() {
@Override
protected void onActive() {
mExecutor.execute(mRefreshRunnable);
}
};
}
这里了创建了一个LiveData对象,并在它的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;
//调用了compute()方法,这个方法在构建它对象的时候有实现
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()方法:
@Override
protected PagedList<Value> 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;
}
这里的initializeKey一是在ItemKeyedDataSource中会用到,在使用DataSource.Factory是,里面的create()方法就是这个时候调用的,可以看出这个方法只调用了一次,接下来就是创建一个PageList对象,这个对象就会通过LiveDate设置到adapter中去,到现在为止,还没给adapter设置数据,别急,接着往下看,PageList是通过Builder创建,最终调用的是PageList的create()方法,先来看看看:
private 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) {
int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
if (!dataSource.isContiguous()) {
//noinspection unchecked
dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
.wrapAsContiguousWithoutPlaceholders();
if (key != null) {
lastLoad = (int) key;
}
}
ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
return new ContiguousPagedList<>(contigDataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
key,
lastLoad);
} else {
return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
(key != null) ? (Integer) key : 0);
}
}
通常创建的是ContiguousPagedList对象,创建这个对象所传的参数都是一开始创建的参数,先来看看这个对象的构造函数做了什么:
ContiguousPagedList(
@NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
final @Nullable K key,
int lastLoad) {
super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
mLastLoad = lastLoad;
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
}
}
这里的mDataSource就是通过DataSource.Factory的create()创建的,这里会调用到它的dispatchLoadInitial()方法,DataSource有三个子类,这里就只看PageKeyedDataSource,其他的两个类似:
@Override
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
LoadInitialCallbackImpl<Key, Value> callback =
new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
loadInitial(new LoadInitialParams<Key>(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);
}
可以看到一开始传递进来的参数这里封装到LoadInitialParams对象中去了,对于PageKeyedDataSource的loadInitial()方法主要是用于一开初始化的数据,一开始设置的参数传递给最终去请求数据。数据请求成功后会调用到callback对象的result()方法,最终反馈到adapter中刷新界面,result()方法里主要做的将线程切换到主线程中来,最后调用的是PageResult.Receiver的onPageResult方法,PageResult.Receiver是一个抽象类,在ContiguousPagedList实现并传递过去的,这里就来看下这里面做了什么:
private 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 (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);
}
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);
}
}
};
返回的数据在这里取出来了,并传递到mStorage中去了,这是一个PagedStorage对象,那就在跟到PagedStorage去看看它的init()方法,其他的两个方法类似,感兴趣的可以自己去看下:
void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
@NonNull Callback callback) {
init(leadingNulls, page, trailingNulls, positionOffset);
callback.onInitialized(size());
}
private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) {
mLeadingNullCount = leadingNulls;
mPages.clear();
mPages.add(page);
mTrailingNullCount = trailingNulls;
mPositionOffset = positionOffset;
mStorageCount = page.size();
// initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
// even if it will break if nulls convert.
mPageSize = page.size();
mNumberPrepended = 0;
mNumberAppended = 0;
}
这里存储数据的是mPages是一个ArrayList<List<T>>对象,其他的变量存储的是一些相关的数据,接着调用到了callback的onInitialized()方法,这个方法在ContiguousPagedList有实现:
public void onInitialized(int count) {
notifyInserted(0, count);
}
很简单就是调用了他自己的notifyInserted()方法:
void notifyInserted(int position, int count) {
if (count != 0) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
Callback callback = mCallbacks.get(i).get();
if (callback != null) {
callback.onInserted(position, count);
}
}
}
}
仔细一瞧,又是一个回调,那这个回调是哪里添加进来的呢?还记得PagedListAdapter的submitList么,对了,就是在这个方法里面添加的回调,PagedListAdapter里面使用的是代理模式,实际的功能是AsyncPagedListDiffer来处理的,所以这里就来看看AsyncPagedListDiffer的submitList()方法:
public void submitList(final PagedList<T> pagedList) {
if (pagedList != null) {
if (mPagedList == null && mSnapshot == null) {
mIsContiguous = pagedList.isContiguous();
} else {
if (pagedList.isContiguous() != mIsContiguous) {
throw new IllegalArgumentException("AsyncPagedListDiffer cannot handle both"
+ " contiguous and non-contiguous lists.");
}
}
}
//pagedList如果是同一个是不会往下执行的,所以下拉刷新数据是必须要替换掉pagedList
if (pagedList == mPagedList) {
// nothing to do
return;
}
// incrementing generation means any currently-running diffs are discarded when they finish
final int runGeneration = ++mMaxScheduledGeneration;
//传进来的pageList为null,会将之前传进来的pageList置为null,如果不置为null那么新传进来的pageList就会与之前的pageList的数据进行对比,将有变化的item数据进行更新
if (pagedList == null) {
int removedCount = getItemCount();
if (mPagedList != null) {
//清除数据传进来的回调
mPagedList.removeWeakCallback(mPagedListCallback);
mPagedList = null;
} else if (mSnapshot != null) {
mSnapshot = null;
}
// dispatch update callback after updating mPagedList/mSnapshot
mUpdateCallback.onRemoved(0, removedCount);
if (mListener != null) {
mListener.onCurrentListChanged(null);
}
return;
}
//首次添加进来的时候会执行到这里,
if (mPagedList == null && mSnapshot == null) {
// fast simple first insert
mPagedList = pagedList;
//这里就是重点了,请求到的数据和界面刷新就是通过添加的这个回调来关联起来的
pagedList.addWeakCallback(null, mPagedListCallback);
// dispatch update callback after updating mPagedList/mSnapshot
mUpdateCallback.onInserted(0, pagedList.size());
if (mListener != null) {
mListener.onCurrentListChanged(pagedList);
}
return;
}
//传进来pageList时,已经有一个已经存在的pageList了,这时就会将之前的pageList拷贝一份出来,同时将回调清空掉
if (mPagedList != null) {
// first update scheduled on this list, so capture mPages as a snapshot, removing
// callbacks so we don't have resolve updates against a moving target
mPagedList.removeWeakCallback(mPagedListCallback);
mSnapshot = (PagedList<T>) mPagedList.snapshot();
mPagedList = null;
}
if (mSnapshot == null || mPagedList != null) {
throw new IllegalStateException("must be in snapshot state to diff");
}
final PagedList<T> oldSnapshot = mSnapshot;
final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
//有两个pageList时,会执行到这里,将对比的任务放到子线程去执行
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result;
result = PagedStorageDiffHelper.computeDiff(
oldSnapshot.mStorage,
newSnapshot.mStorage,
mConfig.getDiffCallback());
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
latchPagedList(pagedList, newSnapshot, result);
}
}
});
}
});
}
上面有个方法需要注意,pagedList.addWeakCallback(),在这里添加了一格回调,这个回调就是前面说到的,当数据请求完成时,就会调用到这个回调,现在就来看下在这个回调里面具体做了什么:
private PagedList.Callback mPagedListCallback = new PagedList.Callback() {
@Override
public void onInserted(int position, int count) {
mUpdateCallback.onInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mUpdateCallback.onRemoved(position, count);
}
@Override
public void onChanged(int position, int count) {
// NOTE: pass a null payload to convey null -> item
mUpdateCallback.onChanged(position, count, null);
}
};
使用的也是代理,具体的操作交给了AdapterListUpdateCallback去执行,来看看这里面做了些什么东西:
public final class AdapterListUpdateCallback implements ListUpdateCallback {
@NonNull
private final Adapter mAdapter;
public AdapterListUpdateCallback(@NonNull Adapter adapter) {
this.mAdapter = adapter;
}
public void onInserted(int position, int count) {
this.mAdapter.notifyItemRangeInserted(position, count);
}
public void onRemoved(int position, int count) {
this.mAdapter.notifyItemRangeRemoved(position, count);
}
public void onMoved(int fromPosition, int toPosition) {
this.mAdapter.notifyItemMoved(fromPosition, toPosition);
}
public void onChanged(int position, int count, Object payload) {
this.mAdapter.notifyItemRangeChanged(position, count, payload);
}
}
这一看就能明白,调用的是RecyclerView.Adapter的方法,也就是去刷新界面了,到这初始加载数据的流程就已经走完了一遍,接下来还有一个问题,那就是paging是如何是实现自动加载数据的,要想明白这个问题,那就得先来看看adapter的getItem()方法了,这个方法是获取对应item的数据,先来看看:
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()方法,在这个方法里面就会去判断当前需不需要去加载数据,在PagedList.Config.Builder里面有个setPrefetchDistance()方法,这个方法就是设置距离边界还有多少个item就会开始去加载数据,这里就不在跟着进去了,好了,到这就结束了。