首先還是放上官方文件地址:developer.android.google.cn/topic/libra…
如果你還不知道這個庫是可以做什麼事,可以看官方文件事例或者參考我之前寫的這篇文章:
目前該庫還處於測試階段,所以我就按上篇文章的內容進行部分簡單的原始碼分析,從中你應該可以瞭解到該庫的設計思路.
直接進入正題,先寫簡單的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<PagedList<GankData>> mDataLiveData;
public MainData(@NonNull Application application) {
super(application);
}
public LiveData<PagedList<GankData>> getDataLiveData() {
initPageList();
return mDataLiveData;
}
/**
* 初始化pageList.
*/
private void initPageList() {
//獲取dataSource,列表資料都從這裡獲取,
final DataSource<Integer, GankData> tiledDataSource = new TiledDataSource<GankData>() {
/**
* 需要的總個數,如果數量不定,就傳COUNT_UNDEFINED.
*/
@Override
public int countItems() {
return DataSource.COUNT_UNDEFINED;
}
/**
* 返回需要載入的資料.
* 這裡是線上程非同步中執行的,所以我們可以同步請求資料並且返回
* @param startPosition 現在第幾個資料
* @param count 載入的資料數量
*/
@Override
public List<GankData> loadRange(int startPosition, int count) {
List<GankData> gankDataList = new ArrayList<>();
//這裡我們用retrofit獲取資料,每次獲取十條資料,數量不為空,則讓mPage+1
try {
Response<BaseResponse<List<GankData>>> 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<PagedList<GankData>>資料,
mDataLiveData = new LivePagedListProvider<Integer, GankData>() {
@Override
protected DataSource<Integer, GankData> 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之前已經講過了,如果你還不知道這個原理,可以去看下我之前文章:juejin.im/post/5a03ed…
首先我們來看下PageList到底是什麼:首先PageList是個抽象類,並且提供Build模式填充資料,首先看下它的build類:
public static class Builder<Key, Value> {
private DataSource<Key, Value> mDataSource;
private Executor mMainThreadExecutor;
private Executor mBackgroundThreadExecutor;
private Config mConfig;
private Key mInitialKey;
/**
* Creates a {@link PagedList} with the given parameters.
* <p>
* This call will initial data and perform any counting needed to initialize the PagedList,
* therefore it should only be called on a worker thread.
* <p>
* 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<Value> 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}.
* <p>
* 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 subclasses
DataSource() {
}
/**
* 數量不定的標誌.
*/
@SuppressWarnings("WeakerAccess")
public static int COUNT_UNDEFINED = -1;
/**
* 總數量(數量不定返回COUNT_UNDEFINED).
*/
@WorkerThread
public 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.
* <p>
* 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.
* <p>
* 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<InvalidatedCallback> mOnInvalidatedCallbacks =
new CopyOnWriteArrayList<>();
/**
* Add a callback to invoke when the DataSource is first invalidated.
* <p>
* Once invalidated, a data source will not become valid again.
* <p>
* 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);
}
/**
* 對外方法,執行回撥(但是未發現任何地方呼叫了這個方法,估計是為了以後擴充套件用).
*/
@AnyThread
public 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.
*/
@WorkerThread
public 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 TiledDataSource<Type> extends DataSource<Integer, Type> {
@WorkerThread
@Override
public abstract int countItems();
@Override
boolean isContiguous() {
return false;
}
@WorkerThread
public abstract List<Type> loadRange(int startPosition, int count);
final List<Type> loadRangeWrapper(int startPosition, int count) {
if (isInvalid()) {
return null;
}
List<Type> list = loadRange(startPosition, count);
if (isInvalid()) {
return null;
}
return list;
}
ContiguousDataSource<Integer, Type> getAsContiguous() {
return new TiledAsBoundedDataSource<>(this);
}
/**
* BoundedDataSource是最終繼承ContiguousDataSource的類,這個負責包裝TiledDataSource.
*/
static class TiledAsBoundedDataSource<Value> extends BoundedDataSource<Value> {
final TiledDataSource<Value> mTiledDataSource;
TiledAsBoundedDataSource(TiledDataSource<Value> tiledDataSource) {
mTiledDataSource = tiledDataSource;
}
@WorkerThread
@Nullable
@Override
public List<Value> loadRange(int startPosition, int loadCount) {
return mTiledDataSource.loadRange(startPosition, loadCount);
}
}
}複製程式碼
loadRange方法我們用不到暫時(沒發現呼叫這個方法的地方),我們只需要實現countItems和loadRange方法,我們例子中是無限制數量,所以傳了-1,而loadRange方法是在非同步執行緒中執行,所以這裡可以用網路同步請求返回資料.
然後接下來看PagedList的create方法.
@NonNull
private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> 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<K, T>) ((TiledDataSource<T>) dataSource).getAsContiguous();
}
ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
return new ContiguousPagedList<>(contigDataSource,
mainThreadExecutor,
backgroundThreadExecutor,
config,
key);
} else {
return new TiledPagedList<>((TiledDataSource<T>) dataSource,
mainThreadExecutor,
backgroundThreadExecutor,
config,
(key != null) ? (Integer) key : 0);
}
}
複製程式碼
根據一系列條件,我們獲取的PagedList是ContiguousPagedList.
然後我們可以來看LiveData<PagedList<GankData>>是怎麼建立的了:
來看下LivePagedListProvider:
public abstract class LivePagedListProvider<Key, Value> {
/**
* 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<Key, Value> createDataSource();
/**
* Creates a LiveData of PagedLists, given the page size.
* <p>
* 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<PagedList<Value>> 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.
* <p>
* 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<PagedList<Value>> create(@Nullable final Key initialLoadKey,
final PagedList.Config config) {
return new ComputableLiveData<PagedList<Value>>() {
@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 = createDataSource();
mDataSource.addInvalidatedCallback(mCallback);
mList = new PagedList.Builder<Key, Value>()
.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<T> {
private final LiveData<T> 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.
* <p>
* 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<T>() {
@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<T> 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 PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private final PagedListAdapterHelper<T> 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<T> diffCallback) {
mHelper = new PagedListAdapterHelper<>(this, diffCallback);
}
@SuppressWarnings("unused, WeakerAccess")
protected PagedListAdapter(@NonNull ListAdapterConfig<T> config) {
mHelper = new PagedListAdapterHelper<>(new ListAdapterHelper.AdapterCallback(this), config);
}
/**
* Set the new list to be displayed.
* <p>
* 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<T> 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.
* <p>
* 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<T> getCurrentList() {
return mHelper.getCurrentList();
}
}複製程式碼
可以看到,PagedListAdapter只是個殼,做事的是PagedListAdapterHelper.
我們主要看PagedListAdapterHelper的這個幾個方法:
public void setList(final PagedList<T> pagedList) {
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<T>) mList.snapshot();
}
final PagedList<T> oldSnapshot = mList;
final List<T> newSnapshot = pagedList.snapshot();
mUpdateScheduled = true;
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result;
if (mIsContiguous) {
result = ContiguousDiffHelper.computeDiff(
(NullPaddedList<T>) oldSnapshot, (NullPaddedList<T>) newSnapshot,
mConfig.getDiffCallback(), true);
} else {
result = SparseDiffHelper.computeDiff(
(PageArrayList<T>) oldSnapshot, (PageArrayList<T>) 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<T> newList, List<T> diffSnapshot,
DiffUtil.DiffResult diffResult) {
if (mIsContiguous) {
ContiguousDiffHelper.dispatchDiff(mUpdateCallback,
(NullPaddedList<T>) mList, (ContiguousPagedList<T>) newList, diffResult);
} else {
SparseDiffHelper.dispatchDiff(mUpdateCallback, diffResult);
}
mList = newList;
newList.addWeakCallback((PagedList<T>) diffSnapshot, mPagedListCallback);
}複製程式碼
這個理解成給adapter設定集合,理論上設定一次,這裡對於多次設定集合做了重新整理的比較處理。這裡還是用到了RecylerView的 DifffUtil工具.
當然這個庫的主要功能是在未滑倒底部時候就進行資料載入,讓使用者無感知載入,這個處理方法就在getItem(int index)裡:
@SuppressWarnings("WeakerAccess")
@Nullable
public 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:
@Override
public 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();
}
}
@MainThread
private 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 List<T> data = 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();
}
}
});
}
@MainThread
private void prependImpl(List<T> 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<Callback> 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,讓其使用進行高效重新整理.
目前該庫還是測試階段,裡面還有方法回撥都未被用到,本文只是簡單分析下原始碼流程,不過也貌似開啟了一些新的思路.
由於本人水平也不高,文中也有不少理解錯誤之處,也希望能各位指教,一起學習,一起進步.