Jetpack組件paging庫解讀

簡述

paging是jetpack中一個處理分頁的組件,它和RecyclerView有着很好的兼容性,可是在作TV開發使用Leanback的時候, 遇到了一些問題,那就是paging中使用的adapter是RecyclerView的adapter,可是leanback中使用的adapter則不是 ,因此須要對paging的源碼作必定的修改,既然要修改,就須要對paging有個深刻的瞭解, 下面就深刻paging的源碼看看。java

DataSource

它是對數據來源的封裝,能夠是本地數據源(好比:本地數據庫Room、Realm等)也能夠是遠程的接口,也能夠二者兼而有之。另外paging還提供了三種不一樣類型的DataSource,它們都繼承了DataSource這個抽象類。android

PositionalDataSource

基於固定大小的數據源,根據position位置去獲取數據的方式,例如,在滾動聯繫人列表中跳轉到列表中的特定位置(即跳轉到以特定字母開頭的聯繫人)git

ItemKeyedDataSource

根據Key去加載特定的Item,好比經過第N個Item的id,去加載第N+1個Itemgithub

PageKeyedDataSource

根據頁碼信息去獲取item。其中key爲頁碼信息數據庫

PagedList

這個類的做用是負責從DataSource中獲取數據,而後加載到ui上。它負責怎麼加載,好比首頁加載和分頁加載的配置等。bash

複製代碼

PagedListAdapter

負責ui的展現,和RecyclerView中Adapter的做用相似,可是他會觸發加載更多的邏輯。app

PagedListAdapter

他是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);
    }


複製代碼

ContiguousPagedList.loadAroundInternal

@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裏了。

相關文章
相關標籤/搜索