本文共 18618 字,大约阅读时间需要 62 分钟。
RecyclerView使用paging就是多了对数据的拉取,使得RecyclerView的数据和显示更加的解耦,RecyclerView对paging的使用多了如下几步:
instance = CustomAdapter.getInstance(this); factory = new CustomPageDataSourceFactory<>(); build = new LivePagedListBuilder(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> build() { return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory, ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor); }
就是简单调用了它的create()方法,那就看下它的create()方法:
private staticLiveData > create( @Nullable final Key initialLoadKey,//ItemKeyedDataSource会用到 @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()方法,接下来就去看下ComputableLiveData的构造方法:
public ComputableLiveData(@NonNull Executor executor) { mExecutor = executor; mLiveData = new LiveData() { @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 PagedListcompute() { @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 staticPagedList create(@NonNull DataSource dataSource, @NonNull Executor notifyExecutor, @NonNull Executor fetchExecutor, @Nullable BoundaryCallback 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 ) ((PositionalDataSource ) dataSource) .wrapAsContiguousWithoutPlaceholders(); if (key != null) { lastLoad = (int) key; } } ContiguousDataSource contigDataSource = (ContiguousDataSource ) dataSource; return new ContiguousPagedList<>(contigDataSource, notifyExecutor, fetchExecutor, boundaryCallback, config, key, lastLoad); } else { return new TiledPagedList<>((PositionalDataSource ) dataSource, notifyExecutor, fetchExecutor, boundaryCallback, config, (key != null) ? (Integer) key : 0); } }
通常创建的是ContiguousPagedList对象,创建这个对象所传的参数都是一开始创建的参数,先来看看这个对象的构造函数做了什么:
ContiguousPagedList( @NonNull ContiguousDataSourcedataSource, @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); } }
这里的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.Receiverreceiver) { 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); }
可以看到一开始传递进来的参数这里封装到LoadInitialParams对象中去了,对于PageKeyedDataSource的loadInitial()方法主要是用于一开初始化的数据,一开始设置的参数传递给最终去请求数据。数据请求成功后会调用到callback对象的result()方法,最终反馈到adapter中刷新界面,result()方法里主要做的将线程切换到主线程中来,最后调用的是PageResult.Receiver的onPageResult方法,PageResult.Receiver是一个抽象类,在ContiguousPagedList实现并传递过去的,这里就来看下这里面做了什么:
private PageResult.ReceivermReceiver = new PageResult.Receiver () { // 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 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); } 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 Listpage, int trailingNulls, int positionOffset, @NonNull Callback callback) { init(leadingNulls, page, trailingNulls, positionOffset); callback.onInitialized(size()); } private void init(int leadingNulls, List 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 PagedListpagedList) { 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 ) mPagedList.snapshot(); mPagedList = null; } if (mSnapshot == null || mPagedList != null) { throw new IllegalStateException("must be in snapshot state to diff"); } final PagedList oldSnapshot = mSnapshot; final PagedList newSnapshot = (PagedList ) 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就会开始去加载数据,这里就不在跟着进去了,好了,到这就结束了。
转载地址:http://qwhai.baihongyu.com/