首先还是放上官方文档地址:
如果你还不知道这个库是可以做什么事,可以看官方文档事例或者参考我之前写的这篇文章:
.
目前该库还处于测试阶段,所以我就按上篇文章的内容进行部分简单的源码分析,从中你应该可以了解到该库的设计思路.
直接进入正题,先写简单的demo,首先创建我们的model.
MainData:
public class MainData extends AndroidViewModel { /** * 每次需要10个数据. */ private static final int NEED_NUMBER = 10; /** * 福利第一页. */ private static final int PAGE_FIRST = 1; /** * 分页. */ private int mPage = PAGE_FIRST; /** * 列表数据. */ private LiveData> mDataLiveData; public MainData(@NonNull Application application) { super(application); } public LiveData > getDataLiveData() { initPageList(); return mDataLiveData; } /** * 初始化pageList. */ private void initPageList() { //获取dataSource,列表数据都从这里获取, final DataSource tiledDataSource = new TiledDataSource () { /** * 需要的总个数,如果数量不定,就传COUNT_UNDEFINED. */ @Override public int countItems() { return DataSource.COUNT_UNDEFINED; } /** * 返回需要加载的数据. * 这里是在线程异步中执行的,所以我们可以同步请求数据并且返回 * @param startPosition 现在第几个数据 * @param count 加载的数据数量 */ @Override public List loadRange(int startPosition, int count) { List gankDataList = new ArrayList<>(); //这里我们用retrofit获取数据,每次获取十条数据,数量不为空,则让mPage+1 try { Response
>> execute = RetrofitApi.getInstance().mRetrofit.create(AppService.class) .getWelfare1(mPage, NEED_NUMBER).execute(); gankDataList.addAll(execute.body().getResults()); if (!gankDataList.isEmpty()) { mPage++; } } catch (IOException e) { e.printStackTrace(); } return gankDataList; } }; //这里我们创建LiveData >数据, mDataLiveData = new LivePagedListProvider () { @Override protected DataSource createDataSource() { return tiledDataSource; } }.create(0, new PagedList.Config.Builder() .setPageSize(NEED_NUMBER) //每次加载的数据数量 //距离本页数据几个时候开始加载下一页数据(例如现在加载10个数据,设置prefetchDistance为2,则滑到第八个数据时候开始加载下一页数据). .setPrefetchDistance(NEED_NUMBER) //这里设置是否设置PagedList中的占位符,如果设置为true,我们的数据数量必须固定,由于网络数据数量不固定,所以设置false. .setEnablePlaceholders(false) .build()); }}复制代码
这里我们需要创建LiveData<PagedList<GankData>>这个对象,对于LiveData之前已经讲过了,如果你还不知道这个原理,可以去看下我之前文章:
首先我们来看下PageList到底是什么:首先PageList是个抽象类,并且提供Build模式填充数据,首先看下它的build类:
public static class Builder{ private DataSource mDataSource; private Executor mMainThreadExecutor; private Executor mBackgroundThreadExecutor; private Config mConfig; private Key mInitialKey; /** * Creates a {@link PagedList} with the given parameters. * * This call will initial data and perform any counting needed to initialize the PagedList, * therefore it should only be called on a worker thread. *
* While build() will always return a PagedList, it's important to note that the PagedList * initial load may fail to acquire data from the DataSource. This can happen for example if * the DataSource is invalidated during its initial load. If this happens, the PagedList * will be immediately {@link PagedList#isDetached() detached}, and you can retry * construction (including setting a new DataSource). * * @return The newly constructed PagedList */ @WorkerThread @NonNull public PagedList
build() { if (mDataSource == null) { throw new IllegalArgumentException("DataSource required"); } if (mMainThreadExecutor == null) { throw new IllegalArgumentException("MainThreadExecutor required"); } if (mBackgroundThreadExecutor == null) { throw new IllegalArgumentException("BackgroundThreadExecutor required"); } if (mConfig == null) { throw new IllegalArgumentException("Config required"); } return PagedList.create( mDataSource, mMainThreadExecutor, mBackgroundThreadExecutor, mConfig, mInitialKey); }复制代码
这里我们看到必须填的数据有至少有四个,第一个DataSource,不用说,自己手动要创建的,两个线程池,一个Config和一个key,我们再来看下Config:
public static class Config { final int mPageSize; final int mPrefetchDistance; final boolean mEnablePlaceholders; final int mInitialLoadSizeHint; private Config(int pageSize, int prefetchDistance, boolean enablePlaceholders, int initialLoadSizeHint) { mPageSize = pageSize; mPrefetchDistance = prefetchDistance; mEnablePlaceholders = enablePlaceholders; mInitialLoadSizeHint = initialLoadSizeHint; } /** * Builder class for {@link Config}. ** You must at minimum specify page size with {@link #setPageSize(int)}. */ public static class Builder { private int mPageSize = -1; private int mPrefetchDistance = -1; private int mInitialLoadSizeHint = -1; private boolean mEnablePlaceholders = true; /** * Creates a {@link Config} with the given parameters. * * @return A new Config. */ public Config build() { if (mPageSize < 1) { throw new IllegalArgumentException("Page size must be a positive number"); } if (mPrefetchDistance < 0) { mPrefetchDistance = mPageSize; } if (mInitialLoadSizeHint < 0) { mInitialLoadSizeHint = mPageSize * 3; } if (!mEnablePlaceholders && mPrefetchDistance == 0) { throw new IllegalArgumentException("Placeholders and prefetch are the only ways" + " to trigger loading of more data in the PagedList, so either" + " placeholders must be enabled, or prefetch distance must be > 0."); } return new Config(mPageSize, mPrefetchDistance, mEnablePlaceholders, mInitialLoadSizeHint); } }复制代码
这里同样采用Build模式,看下参数:mPageSize代表每次加载数量,mPrefetchDistance代表距离最后多少item数量开始加载下一页,mInittialLoadSizeHint代表首次加载数量(但是需要配合KeyedDataSource,我们现在用TiledDataSource,所以忽略这个属性),mEnablePlaceholders代表是否设置null占位符,需要加载固定数量时候可以设置为true,如果数量不固定则设为false.
因为后面涉及到DataSource,所以我们接下来先分析TiledDataSource.
TiledDataSource的父类DataSource.
// Since we currently rely on implementation details of two implementations,// prevent external subclassing, except through exposed subclassesDataSource() {}/** * 数量不定的标志. */@SuppressWarnings("WeakerAccess")public static int COUNT_UNDEFINED = -1;/** * 总数量(数量不定返回COUNT_UNDEFINED). */@WorkerThreadpublic abstract int countItems();/** * Returns true if the data source guaranteed to produce a contiguous set of items, * never producing gaps. */abstract boolean isContiguous();/** * Invalidation callback for DataSource. ** Used to signal when a DataSource a data source has become invalid, and that a new data source * is needed to continue loading data. */public interface InvalidatedCallback { /** * Called when the data backing the list has become invalid. This callback is typically used * to signal that a new data source is needed. *
* This callback will be invoked on the thread that calls {@link #invalidate()}. It is valid * for the data source to invalidate itself during its load methods, or for an outside * source to invalidate it. */ @AnyThread void onInvalidated();}/** * 数据可用的标志. */private AtomicBoolean mInvalid = new AtomicBoolean(false);/** * 回调的线程安全集合. */private CopyOnWriteArrayList
mOnInvalidatedCallbacks = new CopyOnWriteArrayList<>();/** * Add a callback to invoke when the DataSource is first invalidated. * * Once invalidated, a data source will not become valid again. *
* A data source will only invoke its callbacks once - the first time {@link #invalidate()} * is called, on that thread. * * @param onInvalidatedCallback The callback, will be invoked on thread that * {@link #invalidate()} is called on. */@AnyThread@SuppressWarnings("WeakerAccess")public void addInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) { mOnInvalidatedCallbacks.add(onInvalidatedCallback);}/** * Remove a previously added invalidate callback. * * @param onInvalidatedCallback The previously added callback. */@AnyThread@SuppressWarnings("WeakerAccess")public void removeInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) { mOnInvalidatedCallbacks.remove(onInvalidatedCallback);}/** * 对外方法,执行回调(但是未发现任何地方调用了这个方法,估计是为了以后扩展用). */@AnyThreadpublic void invalidate() { if (mInvalid.compareAndSet(false, true)) { for (InvalidatedCallback callback : mOnInvalidatedCallbacks) { callback.onInvalidated(); } }}/** * Returns true if the data source is invalid, and can no longer be queried for data. * * @return True if the data source is invalid, and can no longer return data. */@WorkerThreadpublic boolean isInvalid() { return mInvalid.get();}复制代码
这里需要先说下AtomicBoolean和CopyOrWriteArrayList,
AtomicBoolean:按照文档说明,大概意思就是以原子的方式更新bollean值,其中
- compareAndSet(boolean expect, boolean update)这个方法,
- 这个方法主要两个作用 1. 比较AtomicBoolean和expect的值,如果一致,执行方法内的语句。其实就是一个if语句 2. 把AtomicBoolean的值设成update 比较最要的是这两件事是一气呵成的,这连个动作之间不会被打断,任何内部或者外部的语句都不可能在两个动作之间运行。为多线程的控制提供了解决的方案。
CopyOrWriteArrayList是一个线程安全的list,它的 add和remove等一些方法都是加锁了.
再来看下TiledDataSource:
public abstract class TiledDataSourceextends DataSource { @WorkerThread @Override public abstract int countItems(); @Override boolean isContiguous() { return false; } @WorkerThread public abstract List loadRange(int startPosition, int count); final List loadRangeWrapper(int startPosition, int count) { if (isInvalid()) { return null; } List list = loadRange(startPosition, count); if (isInvalid()) { return null; } return list; } ContiguousDataSource getAsContiguous() { return new TiledAsBoundedDataSource<>(this); } /** * BoundedDataSource是最终继承ContiguousDataSource的类,这个负责包装TiledDataSource. */ static class TiledAsBoundedDataSource extends BoundedDataSource { final TiledDataSource mTiledDataSource; TiledAsBoundedDataSource(TiledDataSource tiledDataSource) { mTiledDataSource = tiledDataSource; } @WorkerThread @Nullable @Override public List loadRange(int startPosition, int loadCount) { return mTiledDataSource.loadRange(startPosition, loadCount); } }}复制代码
loadRange方法我们用不到暂时(没发现调用这个方法的地方),我们只需要实现countItems和loadRange方法,我们例子中是无限制数量,所以传了-1,而loadRange方法是在异步线程中执行,所以这里可以用网络同步请求返回数据.
然后接下来看PagedList的create方法.
@NonNullprivate staticPagedList create(@NonNull DataSource dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, @NonNull Config config, @Nullable K key) { if (dataSource.isContiguous() || !config.mEnablePlaceholders) { if (!dataSource.isContiguous()) { //noinspection unchecked dataSource = (DataSource ) ((TiledDataSource ) dataSource).getAsContiguous(); } ContiguousDataSource contigDataSource = (ContiguousDataSource ) dataSource; return new ContiguousPagedList<>(contigDataSource, mainThreadExecutor, backgroundThreadExecutor, config, key); } else { return new TiledPagedList<>((TiledDataSource ) dataSource, mainThreadExecutor, backgroundThreadExecutor, config, (key != null) ? (Integer) key : 0); }}复制代码
根据一系列条件,我们获取的PagedList是ContiguousPagedList.
然后我们可以来看LiveData<PagedList<GankData>>是怎么创建的了:
来看下LivePagedListProvider:
public abstract class LivePagedListProvider{ /** * Construct a new data source to be wrapped in a new PagedList, which will be returned * through the LiveData. * * @return The data source. */ @WorkerThread protected abstract DataSource createDataSource(); /** * Creates a LiveData of PagedLists, given the page size. * * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a * {@link android.support.v7.widget.RecyclerView}. * * @param initialLoadKey Initial key used to load initial data from the data source. * @param pageSize Page size defining how many items are loaded from a data source at a time. * Recommended to be multiple times the size of item displayed at once. * * @return The LiveData of PagedLists. */ @AnyThread @NonNull public LiveData
> create(@Nullable Key initialLoadKey, int pageSize) { return create(initialLoadKey, new PagedList.Config.Builder() .setPageSize(pageSize) .build()); } /** * Creates a LiveData of PagedLists, given the PagedList.Config. * * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a * {@link android.support.v7.widget.RecyclerView}. * * @param initialLoadKey Initial key to pass to the data source to initialize data with. * @param config PagedList.Config to use with created PagedLists. This specifies how the * lists will load data. * * @return The LiveData of PagedLists. */ @AnyThread @NonNull public LiveData
> create(@Nullable final Key initialLoadKey, final PagedList.Config config) { return new ComputableLiveData >() { @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 = createDataSource(); mDataSource.addInvalidatedCallback(mCallback); mList = new PagedList.Builder () .setDataSource(mDataSource) .setMainThreadExecutor(ArchTaskExecutor.getMainThreadExecutor()) .setBackgroundThreadExecutor( ArchTaskExecutor.getIOThreadExecutor()) .setConfig(config) .setInitialKey(initializeKey) .build(); } while (mList.isDetached()); return mList; } }.getLiveData(); }复制代码
代码很简单(忽略那些mCallback,现阶段完全用不到),只要我们重写传入DataSource,创建的主要类是ComputableLiveData干得,看一下:
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)public abstract class ComputableLiveData{ private final LiveData mLiveData; private AtomicBoolean mInvalid = new AtomicBoolean(true); private AtomicBoolean mComputing = new AtomicBoolean(false); /** * Creates a computable live data which is computed when there are active observers. * * It can also be invalidated via {@link #invalidate()} which will result in a call to * {@link #compute()} if there are active observers (or when they start observing) */ @SuppressWarnings("WeakerAccess") public ComputableLiveData() { mLiveData = new LiveData
() { @Override protected void onActive() { // TODO if we make this class public, we should accept an executor ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable); } }; } /** * Returns the LiveData managed by this class. * * @return A LiveData that is controlled by ComputableLiveData. */ @SuppressWarnings("WeakerAccess") @NonNull public LiveData getLiveData() { return mLiveData; } /** * mInvalid默认为true,这里可以一条线执行下去,PagedList就是上面那个compute里新建的对象, * 并且执行了postValue方法,观察者那边可以就收到通知了. */ @VisibleForTesting 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()); } }; // invalidation check always happens on the main thread @VisibleForTesting final Runnable mInvalidationRunnable = new Runnable() { @MainThread @Override public void run() { boolean isActive = mLiveData.hasActiveObservers(); if (mInvalid.compareAndSet(false, true)) { if (isActive) { // TODO if we make this class public, we should accept an executor. ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable); } } } };}复制代码
代码也是很简单的,先看构造方法,构造方法中直接新建了LiveData,并且在生命周期onStrart后执行mRefreshRunable线程,这个线程直接给LiveData赋值,然后activity注册的地方就可以收到PagedList的回调了.
我们再来看看构造PagedList的默认线程池,追踪代码:
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)public class DefaultTaskExecutor extends TaskExecutor { private final Object mLock = new Object(); private ExecutorService mDiskIO = Executors.newFixedThreadPool(2); @Nullable private volatile Handler mMainHandler; @Override public void executeOnDiskIO(Runnable runnable) { mDiskIO.execute(runnable); } @Override public void postToMainThread(Runnable runnable) { if (mMainHandler == null) { synchronized (mLock) { if (mMainHandler == null) { mMainHandler = new Handler(Looper.getMainLooper()); } } } //noinspection ConstantConditions mMainHandler.post(runnable); } @Override public boolean isMainThread() { return Looper.getMainLooper().getThread() == Thread.currentThread(); }}复制代码
就是这个Executors.newFixedThreadPool(2)线程池了,
接下来我们看PagedList:
public abstract class PagedListAdapterextends RecyclerView.Adapter { private final PagedListAdapterHelper mHelper; /** * Creates a PagedListAdapter with default threading and * {@link android.support.v7.util.ListUpdateCallback}. * * Convenience for {@link #PagedListAdapter(ListAdapterConfig)}, which uses default threading * behavior. * * @param diffCallback The {@link DiffCallback} instance to compare items in the list. */ protected PagedListAdapter(@NonNull DiffCallback diffCallback) { mHelper = new PagedListAdapterHelper<>(this, diffCallback); } @SuppressWarnings("unused, WeakerAccess") protected PagedListAdapter(@NonNull ListAdapterConfig config) { mHelper = new PagedListAdapterHelper<>(new ListAdapterHelper.AdapterCallback(this), config); } /** * Set the new list to be displayed. * * If a list is already being displayed, a diff will be computed on a background thread, which * will dispatch Adapter.notifyItem events on the main thread. * * @param pagedList The new list to be displayed. */ public void setList(PagedList
pagedList) { mHelper.setList(pagedList); } @Nullable protected T getItem(int position) { return mHelper.getItem(position); } @Override public int getItemCount() { return mHelper.getItemCount(); } /** * Returns the list currently being displayed by the Adapter. * * This is not necessarily the most recent list passed to {@link #setList(PagedList)}, because a * diff is computed asynchronously between the new list and the current list before updating the * currentList value. * * @return The list currently being displayed. */ @Nullable public PagedList
getCurrentList() { return mHelper.getCurrentList(); }}复制代码
可以看到,PagedListAdapter只是个壳,做事的是PagedListAdapterHelper.
我们主要看PagedListAdapterHelper的这个几个方法:
public void setList(final PagedListpagedList) { if (pagedList != null) { if (mList == null) { mIsContiguous = pagedList.isContiguous(); } else { if (pagedList.isContiguous() != mIsContiguous) { throw new IllegalArgumentException("AdapterHelper cannot handle both contiguous" + " and non-contiguous lists."); } } } if (pagedList == mList) { // nothing to do return; } // incrementing generation means any currently-running diffs are discarded when they finish final int runGeneration = ++mMaxScheduledGeneration; if (pagedList == null) { mUpdateCallback.onRemoved(0, mList.size()); mList.removeWeakCallback(mPagedListCallback); mList = null; return; } if (mList == null) { // fast simple first insert mUpdateCallback.onInserted(0, pagedList.size()); mList = pagedList; pagedList.addWeakCallback(null, mPagedListCallback); return; } if (!mList.isImmutable()) { // 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 mList.removeWeakCallback(mPagedListCallback); mList = (PagedList ) mList.snapshot(); } final PagedList oldSnapshot = mList; final List newSnapshot = pagedList.snapshot(); mUpdateScheduled = true; mConfig.getBackgroundThreadExecutor().execute(new Runnable() { @Override public void run() { final DiffUtil.DiffResult result; if (mIsContiguous) { result = ContiguousDiffHelper.computeDiff( (NullPaddedList ) oldSnapshot, (NullPaddedList ) newSnapshot, mConfig.getDiffCallback(), true); } else { result = SparseDiffHelper.computeDiff( (PageArrayList ) oldSnapshot, (PageArrayList ) newSnapshot, mConfig.getDiffCallback(), true); } mConfig.getMainThreadExecutor().execute(new Runnable() { @Override public void run() { if (mMaxScheduledGeneration == runGeneration) { mUpdateScheduled = false; latchPagedList(pagedList, newSnapshot, result); } } }); } });}private void latchPagedList( PagedList newList, List diffSnapshot, DiffUtil.DiffResult diffResult) { if (mIsContiguous) { ContiguousDiffHelper.dispatchDiff(mUpdateCallback, (NullPaddedList ) mList, (ContiguousPagedList ) newList, diffResult); } else { SparseDiffHelper.dispatchDiff(mUpdateCallback, diffResult); } mList = newList; newList.addWeakCallback((PagedList ) diffSnapshot, mPagedListCallback);}复制代码
这个理解成给adapter设置集合,理论上设置一次,这里对于多次设置集合做了刷新的比较处理。这里还是用到了RecylerView的 DifffUtil工具.
当然这个库的主要功能是在未滑倒底部时候就进行数据加载,让用户无感知加载,这个处理方法就在getItem(int index)里:
@SuppressWarnings("WeakerAccess")@Nullablepublic T getItem(int index) { if (mList == null) { throw new IndexOutOfBoundsException("Item count is zero, getItem() call is invalid"); } mList.loadAround(index); return mList.get(index);}复制代码
recyclerView滑动加载到第几个数据时候就会调用adapter的getItem方法,通过判断当前加载的index位置,还有用户设置的预加载位置,从而进行提前加载数据。这里调用的是PagedList的loadAround方法,现在我们去找这个方法的实现在哪:
ContiguousPageList:
@Overridepublic void loadAround(int index) { mLastLoad = index + mPositionOffset; int prependItems = mConfig.mPrefetchDistance - (index - mLeadingNullCount); int appendItems = index + mConfig.mPrefetchDistance - (mLeadingNullCount + mList.size()); mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested); if (mPrependItemsRequested > 0) { schedulePrepend(); } mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested); if (mAppendItemsRequested > 0) { scheduleAppend(); }}@MainThreadprivate void schedulePrepend() { if (mPrependWorkerRunning) { return; } mPrependWorkerRunning = true; final int position = mLeadingNullCount + mPositionOffset; final T item = mList.get(0); mBackgroundThreadExecutor.execute(new Runnable() { @Override public void run() { if (mDetached.get()) { return; } final Listdata = mDataSource.loadBefore(position, item, mConfig.mPageSize); if (data != null) { mMainThreadExecutor.execute(new Runnable() { @Override public void run() { if (mDetached.get()) { return; } prependImpl(data); } }); } else { detach(); } } });}@MainThreadprivate void prependImpl(List before) { final int count = before.size(); if (count == 0) { // Nothing returned from source, stop loading in this direction return; } Collections.reverse(before); mList.addAll(0, before); final int changedCount = Math.min(mLeadingNullCount, count); final int addedCount = count - changedCount; if (changedCount != 0) { mLeadingNullCount -= changedCount; } mPositionOffset -= addedCount; mNumberPrepended += count; // only try to post more work after fully prepended (with offsets / null counts updated) mPrependItemsRequested -= count; mPrependWorkerRunning = false; if (mPrependItemsRequested > 0) { // not done prepending, keep going schedulePrepend(); } // finally dispatch callbacks, after prepend may have already been scheduled for (WeakReference weakRef : mCallbacks) { Callback callback = weakRef.get(); if (callback != null) { if (changedCount != 0) { callback.onChanged(mLeadingNullCount, changedCount); } if (addedCount != 0) { callback.onInserted(0, addedCount); } } }}复制代码
由于我们传入的是TiledDataSource,所以这些mDataSource的loadBefore和loadAfter等最终都调用了TiledDataSource的loadRange方法。
到这里为止,好像整体流程已经分析完了,整体流程就看这张图吧:
首先Recyclerview设置PagedListAdater,PagedListAdapter设置对应的PagedList,每次adapter getItme就让PagedList知道用户已经滑到第几个item,PagedList计算这些数量以及设置的各种条件,条件达成就通知DataSource,让其返回数据,数据返回成功,通知PagedListAdapter,让其使用进行高效刷新.
目前该库还是测试阶段,里面还有方法回调都未被用到,本文只是简单分析下源码流程,不过也貌似打开了一些新的思路.
由于本人水平也不高,文中也有不少理解错误之处,也希望能各位指教,一起学习,一起进步.