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就會開始去加載資料,這裡就不在跟着進去了,好了,到這就結束了。