Jetpack-Android Paging Library

目錄:

需求來(lái)源
Paging運(yùn)作流程
Paging三大部件
如何使用
三種DataSource對(duì)比
LivePagedListBuilder、RxPagedListBuilder對(duì)比

一、需求來(lái)源:

為方便實(shí)現(xiàn)上拉加載、簡(jiǎn)化代碼、上拉加載邏輯可配置...總之就是為了方便

Paging出現(xiàn)前,上拉加載觸發(fā)一般是通過(guò):

  1. 監(jiān)聽(tīng)RecyclerView的滾動(dòng)事件,判斷RecyclerView是否滾動(dòng)到底部
  2. 處理Adapter的onBindViewHolder方法,根據(jù)位置與數(shù)量判斷當(dāng)前位置item是否該觸發(fā)上拉加載
  3. 或者其他方式...

如方式一:

        recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {

            var shouldReload = false

            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE && shouldReload) {
                    // 加載下一頁(yè)
                    ...
                }
            }

            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                val layoutManager: LinearLayoutManager =
                    recycler_view.layoutManager as LinearLayoutManager
                layoutManager.apply {

                    val firstVisibleItem = findFirstVisibleItemPosition()
                    val visibleItemCount = childCount
                    val totalItemCount = itemCount
                    shouldReload = firstVisibleItem + visibleItemCount == totalItemCount && dy > 0
                }
            }
        })

BaseRecyclerViewAdapterHelper庫(kù)實(shí)現(xiàn)方式為方案二,部分代碼如下:

    @Override
    public void onBindViewHolder(@NonNull K holder, int position) {
        ...  // 其他代碼
        autoLoadMore(position);
        ...  // 其他代碼
    }

    @Override
    public void onBindViewHolder(@NonNull K holder, int position, @NonNull List<Object> payloads) {
        ...  // 其他代碼
        autoLoadMore(position);
        ...  // 其他代碼
    }

    // 自動(dòng)上拉加載判斷邏輯
    private void autoLoadMore(int position) {

        // mPreLoadNumber為預(yù)加載控制數(shù)量,默認(rèn)為1
        if (position < getItemCount() - mPreLoadNumber) {
            return;
        }
        if (mLoadMoreView.getLoadMoreStatus() != LoadMoreView.STATUS_DEFAULT) {
            return;
        }
        
        ... // 其他代碼
        mRequestLoadMoreListener.onLoadMoreRequested();
        ... // 其他代碼
    }

Paging運(yùn)作流程

首先,Adapter會(huì)繼承自PagedListAdapter(DiffUtils)PagedListAdapter(DiffUtils)最終也是繼承自RecyclerView.Adapter。執(zhí)行到onBindViewHolder()數(shù)據(jù)綁定時(shí),調(diào)用getItem(position)獲取當(dāng)前位置的數(shù)據(jù),此時(shí)PagedListAdapter就會(huì)知道列表現(xiàn)在處于什么位置,以及是否觸發(fā)加載下一頁(yè)功能。當(dāng)加載下一頁(yè)功能被觸發(fā)時(shí),會(huì)通知內(nèi)部PagedListDataSource拉取數(shù)據(jù),獲得數(shù)據(jù)后通過(guò)DiffUtils對(duì)比得到最終數(shù)據(jù)集

Paging.png

Paging三大部件

1. PagedListAdapter

繼承自RecyclerView.Adapter,用來(lái)承載PagedList。PagedListAdapter構(gòu)造函數(shù)需提供DiffUtil.ItemCallback對(duì)象,或者是AsyncDifferConfig對(duì)象。

    protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
        mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
        mDiffer.addPagedListListener(mListener);
    }

    protected PagedListAdapter(@NonNull AsyncDifferConfig<T> config) {
        mDiffer = new AsyncPagedListDiffer<>(new AdapterListUpdateCallback(this), config);
        mDiffer.addPagedListListener(mListener);
    }

數(shù)據(jù)對(duì)比主要是用實(shí)例化的AsyncDifferConfig對(duì)象,其構(gòu)造方法如下:

    public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
            @NonNull DiffUtil.ItemCallback<T> diffCallback) {
        mUpdateCallback = new AdapterListUpdateCallback(adapter);
        mConfig = new AsyncDifferConfig.Builder<>(diffCallback).build();
    }

    @SuppressWarnings("WeakerAccess")
    public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback,
            @NonNull AsyncDifferConfig<T> config) {
        mUpdateCallback = listUpdateCallback;
        mConfig = config;
    }

2. PagedList

PagedList是一個(gè)抽象類(lèi),是一個(gè)List的封裝類(lèi)別,與我們熟悉的ArrayList一樣繼承自AbstractList。用來(lái)存儲(chǔ)分頁(yè)載入的數(shù)據(jù)集合,并且通知DataSource數(shù)據(jù)加載時(shí)機(jī)??梢酝ㄟ^(guò)PagedList.Config配置

(1). mPageSize:頁(yè)面大小即每次加載時(shí)加載的數(shù)量
(2). mPrefetDistance:預(yù)取距離,給定UI中最后一個(gè)可見(jiàn)的Item,超過(guò)這個(gè)Item應(yīng)該預(yù)取一段數(shù)據(jù)

PagingConfig.png

3. DataSource

負(fù)責(zé)實(shí)現(xiàn)數(shù)據(jù)載入實(shí)現(xiàn),常用子類(lèi)如下:

  1. PageKeyedDataSource:當(dāng)后一頁(yè)的取得方式從當(dāng)前頁(yè)得知,通過(guò)當(dāng)前頁(yè)相關(guān)的key來(lái)獲取數(shù)據(jù)。

假定一個(gè)場(chǎng)景幫助理解:瀏覽短視頻數(shù)據(jù)時(shí)返回的每個(gè)視頻攜帶視頻類(lèi)別,當(dāng)哪個(gè)視頻觀看比較久,則下一頁(yè)優(yōu)先獲取該類(lèi)別的視頻--后一頁(yè)的請(qǐng)求,根據(jù)前一頁(yè)的key(視頻類(lèi)別)獲取
簡(jiǎn)單常見(jiàn)的按照頁(yè)序號(hào)查詢數(shù)據(jù)也可以用這個(gè)DataSource。初始時(shí)callback設(shè)置前后頁(yè)key為-1,1。加載下一頁(yè)設(shè)置callback的key遞增1,加載上一頁(yè)設(shè)置callback的key遞減1.

  1. PositionalDataSource:通過(guò)在數(shù)據(jù)中的position作為key來(lái)獲取下一頁(yè)數(shù)據(jù)。數(shù)據(jù)排一排,根據(jù)配置的起始位置和獲取數(shù)量,獲取從指定位置開(kāi)始后續(xù)n條數(shù)據(jù)。比如說(shuō),請(qǐng)求返回從第100條數(shù)據(jù)開(kāi)始的之后20條數(shù)據(jù)。

假如設(shè)置初始請(qǐng)求從第30項(xiàng)開(kāi)始,獲取15條數(shù)據(jù)。此時(shí)下一頁(yè)請(qǐng)求參數(shù)為從第45項(xiàng)開(kāi)始,獲取15條數(shù)據(jù)。參數(shù)中startPosition=requestedStartPosition+pageSize

  1. ItemKeyedDataSource:通過(guò)當(dāng)前頁(yè)數(shù)據(jù)信息(具體的item)作為key,來(lái)獲取下一頁(yè)數(shù)據(jù)。必須由當(dāng)前頁(yè)數(shù)據(jù)去加載下一頁(yè)數(shù)據(jù)。

場(chǎng)景:例如小紅書(shū)、資訊類(lèi)的app,上下頁(yè)的請(qǐng)求參數(shù)都是從當(dāng)前頁(yè)配置的

如何使用

1. 定義DiffCallback

boolean areItemsTheSame(@NonNull DataBean oldItem, @NonNull DataBean newItem):判斷是不是同一個(gè)Item
boolean areContentsTheSame(@NonNull DataBean oldItem, @NonNull DataBean newItem):判斷兩個(gè)Item內(nèi)容是否相同,當(dāng)areItemsTheSame為true時(shí)才調(diào)用

2. 初始化適配器,綁定RecyclerView

使用基本與RecyclerView.Adapter一樣

差異點(diǎn):需傳入DiffUtil.ItemCallback。獲取數(shù)據(jù)使用getItem(position)方法

    private class MyAdapter extends PagedListAdapter<DataBean,  ViewHolder> {
        public MyAdapter() {
            super(mDiffCallback);
        }

        ... // 其他代碼

        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            DataBean data = getItem(position);
            ... // 填充Item
        }
    }
3. 設(shè)置PagedList.Config
        PagedList.Config mPagedListConfig = new PagedList.Config.Builder()
                .setPageSize(2)                 // 設(shè)置后續(xù)頁(yè)面(非第一頁(yè))每頁(yè)加載的數(shù)量
                .setPrefetchDistance(2)         // getItem(position)時(shí)調(diào)用
                .setEnablePlaceholders(true)    // 設(shè)置占位符,默認(rèn)true
                .setInitialLoadSizeHint(3)      // 設(shè)置第一頁(yè)加載的數(shù)量
                .build();
4. 設(shè)置DataSource
    private class MyDataSource extends PageKeyedDataSource<Integer, DataBean> {

        @Override
        public void loadInitial(@NonNull final LoadInitialParams<Integer> params,
                                @NonNull final LoadInitialCallback<Integer, DataBean> callback) {
             // 請(qǐng)求第一頁(yè)數(shù)據(jù)
        }

        @Override
        public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, DataBean> callback) {

        }

        @Override
        public void loadAfter(@NonNull final LoadParams<Integer> params,
                              @NonNull final LoadCallback<Integer, DataBean> callback) {
             // 請(qǐng)求下一頁(yè)數(shù)據(jù)
        }
    }
5. 設(shè)置DataSourceFactory
    private class DataSourceFactory extends DataSource.Factory<Integer, DataBean> {

        @NonNull
        @Override
        public DataSource<Integer, DataBean> create() {
            return new MyDataSource();
        }
    }
6. 設(shè)置LiveData
        LivePagedListBuilder<Integer, DataBean> builder = new LivePagedListBuilder<>(
                new DataSourceFactory(), mPagedListConfig);
        LiveData<PagedList<DataBean>> mPagedList = builder.build();
        mPagedList.observe(this, new Observer<PagedList<DataBean>>() {

            @Override
            public void onChanged(PagedList<DataBean> o) {
                // 填充數(shù)據(jù)  
                mAdapter.submitList(o);
            }
        });

三種DataSource對(duì)比

1. PositionalDataSource:根據(jù)列表的絕對(duì)位置獲取數(shù)據(jù)
/**
 * desc:根據(jù)列表的絕對(duì)位置決定放數(shù)據(jù)</br>
 * 從任意指定位置開(kāi)始獲取數(shù)據(jù)
 * time: 2019/11/12-11:25</br>
 * author:Leo </br>
 */
注:其中的泛型Item是請(qǐng)求到列表每項(xiàng)數(shù)據(jù)的實(shí)體類(lèi)
class TestPositionalDataSource : PositionalDataSource<Item>() {

    override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Item>) {
        params.apply {
            val items = getIncreaseItems(startPosition, loadSize)
            callback.onResult(items)
        }
    }

    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Item>) {
        params.apply {
            val items = getIncreaseItems(requestedStartPosition, pageSize)
            callback.onResult(items, 0)
        }
    }
}

該方式的關(guān)鍵點(diǎn)在于LoadInitialParamsLoadRangeParams中參數(shù)的獲取

ContiguousPagedList.java
175行 ContiguousPagedList構(gòu)造函數(shù)
mDataSource.dispatchLoadInitial(key,
     mConfig.initialLoadSizeHint,
     mConfig.pageSize,
     mConfig.enablePlaceholders,
     mMainThreadExecutor,
     mReceiver);
  • LoadInitialParams各參數(shù)與PagedList.Config對(duì)應(yīng)關(guān)系如下:
    // key必須為Integer類(lèi)型
    requestedStartPosition -> LivePagedListBuilder.setInitialLoadKey
    requestedLoadSize -> config.initialLoadSizeHint
    pageSize -> config.pageSize
    placeholdersEnabled -> false // 該模式下寫(xiě)死為false
    PositionalDataSource.java

    final void dispatchLoadInitial(...) {
        ...
        LoadInitialParams params = new LoadInitialParams(
                requestedStartPosition, requestedLoadSize, pageSize, acceptCount);
        loadInitial(params, callback);
        ...
    }

初次加載數(shù)據(jù)時(shí),參數(shù)值讀取的參數(shù)如上構(gòu)造函數(shù)`LoadInitialParams`
注意:此時(shí)placeholdersEnabled實(shí)際為false

LoadRangeParams參數(shù)值來(lái)源

  • 取下一頁(yè) dispatchLoadAfter
    startPosition -> 當(dāng)前位置+1
    loadSize -> config.pageSize
  • 取上一頁(yè) dispatchLoadBefore
    參數(shù)值根據(jù)不同情景賦值,源碼如下
PositionalDataSource.java
@Override
void dispatchLoadBefore(...) {
    int startIndex = currentBeginIndex - 1;
    if (startIndex < 0) {
         // 列表為空 情景一
         mSource.dispatchLoadRange(
               PageResult.PREPEND, startIndex, 0, mainThreadExecutor,receiver);
    } else {
         // 列表不為空 情景二
         int loadSize = Math.min(pageSize, startIndex + 1);
         startIndex = startIndex - loadSize + 1;
         mSource.dispatchLoadRange(
               PageResult.PREPEND, startIndex, loadSize, mainThreadExecutor, receiver);
    }
}

LoadRangeParams參數(shù)值來(lái)源
情景一:此時(shí)列表為空,此時(shí)
    startPosition -> -1 
    loadSize -> 0
情景二:正常加載之前數(shù)據(jù)
    startPosition -> 當(dāng)前位置向前推loadSize個(gè)數(shù)
    loadSize -> 當(dāng)前位置與config.pageSize的最小者(避免可能出現(xiàn)當(dāng)前位置之前的數(shù)量少于pageSize情況)

總結(jié):初始化加載時(shí),參數(shù)從PagedList配置項(xiàng)讀取,無(wú)占位符。加載前后數(shù)據(jù)時(shí),開(kāi)始位置根據(jù)當(dāng)前位置向前/后推算,加載數(shù)量讀取自config.pageSize。該類(lèi)型DataSource無(wú)法加載上一頁(yè)

  • 此時(shí)獲取上頁(yè)/下頁(yè)數(shù)據(jù)僅僅根據(jù)位置序號(hào)就能獲得。

使用注意點(diǎn):
使用PositionalDataSource時(shí),需注意需設(shè)置config.enablePlaceholders=false,否則會(huì)崩潰。原因詳解如下:

    PagedList.class
    @NonNull
    static <K, T> PagedList<T> create(...) {
        /**
         * PositionalDataSource時(shí),dataSource.isContiguous()恒等于false
         * 此時(shí)config.enablePlaceholders=false才執(zhí)行if邏輯
         **/
        if (dataSource.isContiguous() || !config.enablePlaceholders) {
            ...其他代碼
            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);
        }
    }

create方法生成的ContiguousPagedList、TiledPagedList對(duì)象最終都會(huì)調(diào)用PositionalDataSource.dispatchLoadInitial方法,如下:
    final void dispatchLoadInitial(...) {
        /**
         * 主要生成LoadInitialCallbackImpl對(duì)象
         * ContiguousPagedList方式調(diào)用時(shí)傳的acceptCount為false,TiledPagedList傳的為true
         **/
        LoadInitialCallbackImpl<T> callback =
                new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);
        ... 其他代碼
    }

繼續(xù)看LoadInitialCallbackImpl代碼:
    static class LoadInitialCallbackImpl<T> extends LoadInitialCallback<T> {
        ... // 其他代碼
        LoadInitialCallbackImpl(@NonNull PositionalDataSource dataSource, boolean countingEnabled, int pageSize, PageResult.Receiver<T> receiver) {
             ... // 其他代碼

            // ContiguousPagedList方式調(diào)用時(shí)傳的acceptCount為false,TiledPagedList傳的為true
            mCountingEnabled = countingEnabled;
             ... // 其他代碼
        }

        @Override
        public void onResult(@NonNull List<T> data, int position, int totalCount) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                ... // 其他代碼

                if (mCountingEnabled) {
                    int trailingUnloadedCount = totalCount - position - data.size();
                    mCallbackHelper.dispatchResultToReceiver(
                            new PageResult<>(data, position, trailingUnloadedCount, 0));
                }
                ... // 其他代碼
            }
        }

        @Override
        public void onResult(@NonNull List<T> data, int position) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                ... // 其他代碼
                if (mCountingEnabled) {
                    throw new IllegalStateException("Placeholders requested, but totalCount not"
                            + " provided. Please call the three-parameter onResult method, or"
                            + " disable placeholders in the PagedList.Config");
                }
                ... // 其他代碼
            }
        }
    }
從onResult方法代碼可知mCountingEnabled為true一定會(huì)拋出異常。
所以在PagedList.create方法中需保證不創(chuàng)建TiledPagedList對(duì)象,即設(shè)置config.enablePlaceholder=false
2. PageKeyedDataSource:原始數(shù)據(jù)已有分頁(yè)功能,根據(jù)每頁(yè)的Key取得上下頁(yè)數(shù)據(jù)
/**
 * desc:原始數(shù)據(jù)已有分頁(yè)功能,根據(jù)每頁(yè)的Key取得數(shù)據(jù)</br>
 * time: 2019/11/12-11:25</br>
 * author:Leo </br>
 */
注:其中的泛型Int是用于請(qǐng)求上下頁(yè)數(shù)據(jù)的參數(shù)key實(shí)體類(lèi)型
    其中的泛型Item是請(qǐng)求到列表每項(xiàng)數(shù)據(jù)的實(shí)體類(lèi)
class TestPageKeyedDataSource : PageKeyedDataSource<Int, Item>() {
    override fun loadInitial(
        params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Item>
    ) {
        val items = getItems(params.requestedLoadSize)
        callback.onResult(items, items[0].id, items.last().id)
    }

    // 這里的參數(shù)key實(shí)際就是上述的items.last().id
    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Item>) {
        params.apply {
            val items = getIncreaseItems(key, requestedLoadSize)
            callback.onResult(items, items.last().id)
        }
    }

    // 這里的參數(shù)key實(shí)際就是上述的items[0].id
    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Item>) {
        params.apply {
            val items = getReduceItems(key, requestedLoadSize)
            callback.onResult(items, items.first().id)
        }
    }
}

LoadInitialParams參數(shù)與PositionalDataSource一致。但構(gòu)造函數(shù)只取了requestedLoadSizeplaceholdersEnabled這兩個(gè)參數(shù)。調(diào)用代碼如下:

    PageKeyedDataSource.java
    @Override
    final void dispatchLoadInitial(...) {
        LoadInitialCallbackImpl<Key, Value> callback =
                new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
        loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
        ...
    }

注意:該類(lèi)型DataSource,參數(shù)值只取了`requestedLoadSize`和`placeholdersEnabled`這兩個(gè)參數(shù)

LoadParams包含兩個(gè)參數(shù)key(上頁(yè)/下頁(yè)的key) requestedLoadSize(需要加載的數(shù)量)
requestedLoadSize:即為config.pageSize
key:的賦值/初始化時(shí)機(jī)都在上述TestPageKeyedDataSource類(lèi)的幾個(gè)callback.onResult(...)方法中

參數(shù)key的賦值:

    @Override
    final void dispatchLoadAfter(...) {
        @Nullable Key key = getNextKey();
        ...
        loadAfter(new LoadParams<>(key, pageSize),...);
        ...
    }

    @Override
    final void dispatchLoadBefore(...) {
        @Nullable Key key = getPreviousKey();
        ...
        loadBefore(new LoadParams<>(key, pageSize),...);
        ...
    }
上下頁(yè)key的賦值,關(guān)鍵代碼如下:

1. void initKeys(@Nullable Key previousKey, @Nullable Key nextKey)
2. void setPreviousKey(@Nullable Key previousKey)
3. void setNextKey(@Nullable Key nextKey)

initKeys方法在LoadInitialCallback.onResult(...)中。精簡(jiǎn)代碼如下
static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {  
    ...
    LoadInitialCallbackImpl(...) {
        ...
    }

   @Override
   public void onResult(@NonNull List<Value> data, int position, int totalCount,
                @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
        ...
        // setup keys before dispatching data, so guaranteed to be ready
        mDataSource.initKeys(previousPageKey, nextPageKey);
        ...
   }

      @Override
      public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
                @Nullable Key nextPageKey) {
        ...
        mDataSource.initKeys(previousPageKey, nextPageKey);
        ...
   }
}

static class LoadCallbackImpl<Key, Value> extends LoadCallback<Key, Value> {
    ...
    @Override
    public void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
        ...
        if (mCallbackHelper.mResultType == PageResult.APPEND) {
            mDataSource.setNextKey(adjacentPageKey);
        } else {
            mDataSource.setPreviousKey(adjacentPageKey);
        }
        ...
    }
}

總結(jié):初始化加載時(shí),會(huì)設(shè)置上頁(yè)/下頁(yè)的關(guān)鍵值作為獲取上頁(yè)/下頁(yè)數(shù)據(jù)的參數(shù)。每頁(yè)需加載的數(shù)量從config.pageSize獲取

  • 每頁(yè)數(shù)據(jù)都返回兩個(gè)參數(shù):獲取上頁(yè)數(shù)據(jù)的key,獲取下頁(yè)數(shù)據(jù)的key。且獲取每頁(yè)數(shù)據(jù)時(shí)必須傳入key才能請(qǐng)求到該頁(yè)數(shù)據(jù)
  • 只要正確配置previousPageKeynextPageKey(上述代碼中的items[0].id、items.last().id),上下滑動(dòng)都可以加載數(shù)據(jù),在第一頁(yè)時(shí)也能滑動(dòng)
3. ItemKeyedDataSource:當(dāng)列表數(shù)據(jù)的Key有連續(xù)性,可根據(jù)Key找到下一頁(yè)或上一頁(yè)數(shù)據(jù)
/**
 * desc:當(dāng)列表數(shù)據(jù)的Key有連續(xù)性,可根據(jù)Key找到下一頁(yè)或上一頁(yè)數(shù)據(jù)</br>
 * time: 2019/11/12-11:24</br>
 * author:Leo </br>
 */
注:泛型Int即為getKey()方法返回的類(lèi)型
    泛型Item為列表各項(xiàng)的實(shí)體類(lèi)型
class TestItemKeyedDataSource : ItemKeyedDataSource<Int, Item>() {

    // 因?yàn)槊宽?yè)請(qǐng)求都需要key,所以需配置一個(gè)初始key-requestedInitialKey 
    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Item>) {
        params.apply {
            callback.onResult(getIncreaseItems(requestedInitialKey ?: 0, requestedLoadSize))
        }
    }

    // 這里的key是適配器中數(shù)據(jù)集最后一項(xiàng)數(shù)據(jù)的item.id
    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Item>) {
        params.apply {
            callback.onResult(getIncreaseItems(key + 1, requestedLoadSize))
        }
    }

    // 這里的key是適配器中數(shù)據(jù)集第一項(xiàng)數(shù)據(jù)的item.id
    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Item>) {
        params.apply {
            callback.onResult(getIncreaseItems(key, requestedLoadSize))
        }
    }

    override fun getKey(item: Item) = item.id
}

LoadInitialParams參數(shù)與上述兩類(lèi)型參數(shù)相同,但構(gòu)造方法有點(diǎn)區(qū)別,精簡(jiǎn)源碼如下:

    ItemKeyedDataSource.java

    @Override
    final void dispatchLoadInitial(...) {
        ...
        loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
        ...
    }

LoadInitialParams各參數(shù)與PagedList.Config對(duì)應(yīng)關(guān)系如下:
requestedStartPosition -> LivePagedListBuilder.setInitialLoadKey()
requestedLoadSize -> config.initialLoadSizeHint
placeholdersEnabled -> config.enablePlaceholders
注:初次加載數(shù)據(jù)不需指定加載數(shù)據(jù)pageSize

LoadParams包含兩個(gè)參數(shù)key(上頁(yè)/下頁(yè)的key) requestedLoadSize(需要加載的數(shù)量)
requestedLoadSize:即為config.pageSize
key:該key一般都為請(qǐng)求返回實(shí)體類(lèi)中的一個(gè)字段,父類(lèi)根據(jù)上頁(yè)/下頁(yè)拿到緩存下集合中的第一個(gè)或最后一個(gè)數(shù)據(jù)實(shí)體。然后根據(jù)子類(lèi)中重寫(xiě)的getKey(item: T)方法指定key所對(duì)應(yīng)的字段。部分代碼如下:

    ItemKeyedDataSource.java

    @Override
    final void dispatchLoadAfter(...) {
        loadAfter(new LoadParams<>(getKey(currentEndItem), pageSize),...);
    }

    @Override
    final void dispatchLoadBefore(...) {
        loadBefore(new LoadParams<>(getKey(currentBeginItem), pageSize),...);
    }

getKey(currentEndItem)、getKey(currentBeginItem)。getKey為抽象方法,
子類(lèi)重寫(xiě)指定對(duì)應(yīng)字段,如下:

TestItemKeyedDataSource.java
override fun getKey(item: Item) = item.id

總結(jié):初始化加載時(shí),根據(jù)config配置加載當(dāng)前頁(yè)數(shù)據(jù)。渲染數(shù)據(jù)的同時(shí)也會(huì)緩存到Storage中。當(dāng)獲取上頁(yè)/下頁(yè)數(shù)據(jù)時(shí),取得Storage中的第一條/最后一條數(shù)據(jù)。再通過(guò)子類(lèi)重寫(xiě)的getKey方法,拿到請(qǐng)求所需實(shí)際的key。

LivePagedListBuilder、RxPagedListBuilder對(duì)比

從兩個(gè)點(diǎn)對(duì)比:使用方式,值回調(diào)方式

  1. 如何使用
    /**
     * 最終返回的是 LiveData<PagedList<T>>類(lèi)型對(duì)象
     */
    fun allTestLiveData(id: String) = this.let {
        LivePagedListBuilder(
            createFactory(id), PagedList.Config.Builder()
                .setPageSize(10)
                .setInitialLoadSizeHint(13)
                .setEnablePlaceholders(false)
                .setPrefetchDistance(3).build()
        ).build()
    }.observe(this, Observer { adapter.submitList(it) })

    /**
     * 最終返回的是 Flowable<PagedList<T>>類(lèi)型對(duì)象
     */
    fun allTestFlowable(id: String) = this.let {
        RxPagedListBuilder(
            createFactory(id), PagedList.Config.Builder()
                .setPageSize(10)
                .setInitialLoadSizeHint(13)
                .setEnablePlaceholders(false)
                .setPrefetchDistance(3).build()
        ).buildFlowable(BackpressureStrategy.DROP)
    }.subscribe { adapter.submitList(it) }
  1. 值回調(diào)方式
    兩種Builder一個(gè)為L(zhǎng)iveData,一個(gè)為RxJava。
    先看第一個(gè)LivePagedListBuilder
    LivePagedListBuilder通過(guò)build方法構(gòu)造了一個(gè)ComputableLiveData對(duì)象,最終會(huì)執(zhí)行一個(gè)Runnable,看代碼:
ComputableLiveDat.java
    @VisibleForTesting
    final Runnable mRefreshRunnable = new Runnable() {
        @WorkerThread
        @Override
        public void run() {
            boolean computed;
            do {
                ... // 其他代碼
                mLiveData.postValue(value);
                ... // 其他代碼
            } while (computed && mInvalid.get());
        }
    };

關(guān)鍵代碼為postValue(value),通過(guò)LiveData的postValue方法設(shè)置數(shù)據(jù),并在外部監(jiān)聽(tīng)

對(duì)于RxPagedListBuilder,通過(guò)buildObservable方法創(chuàng)建一個(gè)Observable,將其他邏輯代碼放到一個(gè)ObservableOnSubscribe類(lèi)中實(shí)現(xiàn),subscribe回調(diào)方法中實(shí)現(xiàn)PageList的構(gòu)造,代碼如下:

    private PagedList<Value> createPagedList() {
            ... // 其他代碼
            do {
                ... // 其他代碼

                mList = new PagedList.Builder<>(mDataSource, mConfig)
                        .setNotifyExecutor(mNotifyExecutor)
                        .setFetchExecutor(mFetchExecutor)
                        .setBoundaryCallback(mBoundaryCallback)
                        .setInitialKey(initializeKey)
                        .build();
            } while (mList.isDetached());
            return mList;
        }

最終結(jié)果還是為了構(gòu)造一個(gè)PagedList。

兩種方式最終都是為了構(gòu)造一個(gè)PagedList,一個(gè)通過(guò)ComputableLiveData包裹一層通過(guò)在Runnable中調(diào)用LiveData的postValue方法通知值更新;另一個(gè)是借助RxJava的線程切換,創(chuàng)建后通過(guò)設(shè)置觀察者取得結(jié)果。
參考文章:
https://enginebai.com/2019/04/22/android-paging-part1/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容