paging是jetpack中一個處理分頁的組件,它和RecyclerView有着很好的兼容性,可是在作TV開發使用Leanback的時候, 遇到了一些問題,那就是paging中使用的adapter是RecyclerView的adapter,可是leanback中使用的adapter則不是 ,因此須要對paging的源碼作必定的修改,既然要修改,就須要對paging有個深刻的瞭解, 下面就深刻paging的源碼看看。java
它是對數據來源的封裝,能夠是本地數據源(好比:本地數據庫Room、Realm等)也能夠是遠程的接口,也能夠二者兼而有之。另外paging還提供了三種不一樣類型的DataSource,它們都繼承了DataSource這個抽象類。android
基於固定大小的數據源,根據position位置去獲取數據的方式,例如,在滾動聯繫人列表中跳轉到列表中的特定位置(即跳轉到以特定字母開頭的聯繫人)git
根據Key去加載特定的Item,好比經過第N個Item的id,去加載第N+1個Itemgithub
根據頁碼信息去獲取item。其中key爲頁碼信息數據庫
這個類的做用是負責從DataSource中獲取數據,而後加載到ui上。它負責怎麼加載,好比首頁加載和分頁加載的配置等。bash
複製代碼
負責ui的展現,和RecyclerView中Adapter的做用相似,可是他會觸發加載更多的邏輯。app
他是paging中提供的adapter,繼承的是RecyclerView的adapter.可是他裏面的邏輯主要是交給AsyncPagedListDiffer 類去處理了。在建立PagedListAdapter的時候,須要傳入一個DiffUtil.ItemCallback,這個參數的做用是提供RecyclerView 中對新舊數據進行diff計算的條件。async
public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private final AsyncPagedListDiffer<T> mDiffer;
private final AsyncPagedListDiffer.PagedListListener<T> mListener =
new AsyncPagedListDiffer.PagedListListener<T>() {
@Override
public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
PagedListAdapter.this.onCurrentListChanged(currentList);
}
};
/**
* Creates a PagedListAdapter with default threading and
* {@link android.support.v7.util.ListUpdateCallback}.
*
* Convenience for {@link #PagedListAdapter(AsyncDifferConfig)}, which uses default threading
* behavior.
*
* @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to
* compare items in the list.
*/
protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
mDiffer.mListener = mListener;
}
@SuppressWarnings("unused, WeakerAccess")
protected PagedListAdapter(@NonNull AsyncDifferConfig<T> config) {
mDiffer = new AsyncPagedListDiffer<>(new AdapterListUpdateCallback(this), config);
mDiffer.mListener = mListener;
}
/**
* 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 submitList(PagedList<T> pagedList) {
mDiffer.submitList(pagedList);
}
@Nullable
protected T getItem(int position) {
return mDiffer.getItem(position);
}
@Override
public int getItemCount() {
return mDiffer.getItemCount();
}
/**
* Returns the PagedList currently being displayed by the Adapter.
* <p>
* This is not necessarily the most recent list passed to {@link #submitList(PagedList)},
* because a diff is computed asynchronously between the new list and the current list before
* updating the currentList value. May be null if no PagedList is being presented.
*
* @return The list currently being displayed.
*/
@Nullable
public PagedList<T> getCurrentList() {
return mDiffer.getCurrentList();
}
/**
* Called when the current PagedList is updated.
* <p>
* This may be dispatched as part of {@link #submitList(PagedList)} if a background diff isn't
* needed (such as when the first list is passed, or the list is cleared). In either case,
* PagedListAdapter will simply call
* {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}.
* <p>
* This method will <em>not</em>be called when the Adapter switches from presenting a PagedList
* to a snapshot version of the PagedList during a diff. This means you cannot observe each
* PagedList via this method.
*
* @param currentList new PagedList being displayed, may be null.
*/
@SuppressWarnings("WeakerAccess")
public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
}
}
複製代碼
上面已經提到了PagedListAdapter中的主要邏輯都是AsyncPagedListDiffer來完成的,咱們先看看submitList,這個方法的主要做用是經過RecyclerView中DiffUtil工具對比新舊數據的差別,經過差別結果高效的刷新ui, 關於DiffUtil的使用和原理介紹能夠看我以前的文章。ide
AsyncPagedListDiffer.java
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.");
}
}
}
if (pagedList == mPagedList) {
// 若是新舊數據相同,則什麼都不作
return;
}
// incrementing generation means any currently-running diffs are discarded when they finish
final int runGeneration = ++mMaxScheduledGeneration;
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
//若是傳入的數據爲null,則將原始數據所有清除完
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
//若是原始數據爲null,則直接將新加的數據插入
mUpdateCallback.onInserted(0, pagedList.size());
if (mListener != null) {
mListener.onCurrentListChanged(pagedList);
}
return;
}
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(); //在工做線程中作新舊數據的對比工做 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) { //在主線程中根據diff結果,更新ui latchPagedList(pagedList, newSnapshot, result); } } }); } }); } 複製代碼
submitList方法的使用場景是,當數據集發生變化時,去更新ui,結合LiveData使用,咱們只須要關心數據源的變化就能夠,它會自動的去更新ui,由於使用的RecyclerView的DiffUtil,也是的刷新的效率提升了很多。工具
文章的開頭已經說了,paging是一個輔助RecyclerView翻頁的庫,那麼羨慕就看看這個核心的邏輯。
從AsyncPagedListDiffer這個類的getItem方法開始
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);
}
複製代碼
public void loadAround(int index) {
mLastLoad = index + getPositionOffset();
loadAroundInternal(index);
mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
/*
* mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
* dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
* and accesses happen near the boundaries.
*
* Note: we post here, since RecyclerView may want to add items in response, and this
* call occurs in PagedListAdapter bind.
*/
tryDispatchBoundaryCallbacks(true);
}
複製代碼
@MainThread
@Override
protected void loadAroundInternal(int index) {
int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
int appendItems = index + mConfig.prefetchDistance
- (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0) {
schedulePrepend();
}
mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
if (mAppendItemsRequested > 0) {
//加載更多
scheduleAppend();
}
}
複製代碼
@MainThread
private void scheduleAppend() {
if (mAppendWorkerRunning) {
return;
}
mAppendWorkerRunning = true;
final int position = mStorage.getLeadingNullCount()
+ mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
// safe to access first item here - mStorage can't be empty if we're appending
final V item = mStorage.getLastLoadedItem();
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (isDetached()) {
return;
}
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
mMainThreadExecutor, mReceiver);
}
}
});
}
複製代碼
PageKeyedDataSource.dispatchLoadAfter
@Override
final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
int pageSize, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
@Nullable Key key = getNextKey();
if (key != null) {
loadAfter(new LoadParams<>(key, pageSize),
new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
}
}
複製代碼
這個loadAfter方法就是須要咱們本身去實現的,他在一個獨立的線程中執行,咱們在這個方法中要作的操做就是獲取更多數據。而後調用LoadCallbackImpl.onResult的方法
public void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
if (mCallbackHelper.mResultType == PageResult.APPEND) {
mDataSource.setNextKey(adjacentPageKey);
} else {
mDataSource.setPreviousKey(adjacentPageKey);
}
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
}
}
複製代碼
@AnyThread
public void onPageResult(int resultType, @NonNull PageResult<V> pageResult) {
if (pageResult.isInvalid()) {
ContiguousPagedList.this.detach();
} else if (!ContiguousPagedList.this.isDetached()) {
List<V> page = pageResult.page;
if (resultType == 0) {
ContiguousPagedList.this.mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls, pageResult.positionOffset, ContiguousPagedList.this);
if (ContiguousPagedList.this.mLastLoad == -1) {
ContiguousPagedList.this.mLastLoad = pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
}
} else if (resultType == 1) {
ContiguousPagedList.this.mStorage.appendPage(page, ContiguousPagedList.this);
} else {
if (resultType != 2) {
throw new IllegalArgumentException("unexpected resultType " + resultType);
}
ContiguousPagedList.this.mStorage.prependPage(page, ContiguousPagedList.this);
}
if (ContiguousPagedList.this.mBoundaryCallback != null) {
boolean deferEmpty = ContiguousPagedList.this.mStorage.size() == 0;
boolean deferBegin = !deferEmpty && resultType == 2 && pageResult.page.size() == 0;
boolean deferEnd = !deferEmpty && resultType == 1 && pageResult.page.size() == 0;
ContiguousPagedList.this.deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
}
}
}
複製代碼
void appendPage(@NonNull List<T> page, @NonNull Callback callback) {
final int count = page.size();
if (count == 0) {
// Nothing returned from source, stop loading in this direction
return;
}
if (mPageSize > 0) {
// if the previous page was smaller than mPageSize,
// or if this page is larger than the previous, disable tiling
if (mPages.get(mPages.size() - 1).size() != mPageSize
|| count > mPageSize) {
mPageSize = -1;
}
}
mPages.add(page);
mStorageCount += count;
final int changedCount = Math.min(mTrailingNullCount, count);
final int addedCount = count - changedCount;
if (changedCount != 0) {
mTrailingNullCount -= changedCount;
}
mNumberAppended += count;
callback.onPageAppended(mLeadingNullCount + mStorageCount - count,
changedCount, addedCount);
}
複製代碼
@MainThread
@Override
public void onPageAppended(int endPosition, int changedCount, int addedCount) {
// consider whether to post more work, now that a page is fully appended
mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
mAppendWorkerRunning = false;
if (mAppendItemsRequested > 0) {
// not done appending, keep going
scheduleAppend();
}
// finally dispatch callbacks, after append may have already been scheduled
notifyChanged(endPosition, changedCount);
notifyInserted(endPosition + changedCount, addedCount);
}
複製代碼
在這裏發現,最終又回到notify***方法中,這個時候加載更多的數據就已經加到RecyclerView裏了。