Paging 使用及遇到的問題

Paging

概述

Jetpack 的一個分頁庫,幫助開發者更好的分離ui和數據獲取的邏輯,下降項目的耦合。本文主要描述從服務器直接獲取數據git

項目地址:github.com/Tkorn/kotli…github

庫架構

  • DataSource.Factory, 顧名思義,就是數據源工廠。抽象類,須要咱們實現create() 方法,返回DataSource 對象。**注意:create() 每次被調用都應該返回新對象,**否則調用invalidate() 時不只不能刷新列表還會出現死循環。(緣由後面再解釋)
fun createDataSourceFactory(dataSource: DataSource<Long, UserBean>):DataSource.Factory<Long, UserBean>{
        return object: DataSource.Factory<Long,UserBean>(){
            override fun create(): DataSource<Long, UserBean> {
                return dataSource
            }
        }
    }
複製代碼
  • DataSource, Paging已經幫咱們提供了三個很是全面的實現,分別是:bash

    • PageKeyedDataSource: 經過當前頁相關的key來獲取數據,很是常見的是key做爲請求的page的大小。
    • ItemKeyedDataSource: 經過具體item數據做爲key,來獲取下一頁數據。例如聊天會話,請求下一頁數據可能須要上一條數據的id。
    • PositionalDataSource: 經過在數據中的position做爲key,來獲取下一頁數據。這個典型的就是上面所說的在Database中的運用。

    這裏咱們只介紹一下 ItemKeyedDataSource 的使用。 ItemKeyedDataSource 一樣是一個抽象類,須要咱們實現如下四個方法:服務器

    • loadInitial(LoadInitialParams params,LoadInitialCallback callback) 加載初始(第一頁)數據
    • loadAfter(LoadParams params,LoadCallback callback) 加載下一頁數據
    • loadBefore(LoadParams params,LoadCallback callback) 加載上一頁數據,通常用在新聞資訊類列表,把請求的數據加載到列表前面。不須要就空着
    • Key getKey(Value item)
  • PagedList, 分頁庫的關鍵組件是 PagedList 類,用於加載應用數據塊或頁面。隨着所需數據的增多,系統會將其分頁到現有的 PagedList 對象中。若是任何已加載的數據發生更改,會從 LiveData 或基於 RxJava2 的對象向可觀察數據存儲器發出一個新的 PagedList 實例。隨着 PagedList 對象的生成,應用界面會呈現其內容,同時還會考慮界面控件的生命週期。(官方翻譯) 能夠經過 LivePagedListBuilder 建立markdown

val userLiveData =
        LivePagedListBuilder(mRepository.createDataSourceFactory(createDataSource()),
            mRepository.createConfig())
            .setInitialLoadKey(1)
            .build()
複製代碼

LivePagedListBuilder(DataSource.Factory<Key, Value> dataSourceFactory, PagedList.Config config)架構

LivePagedListBuilder 須要DataSource.Factory(上面介紹了)、PagedList.Config(提供分頁須要的參數)mvvm

Config(
    int pageSize, //一頁加載多少數據
    int prefetchDistance,//加載到第幾條時請求下一頁數據
    boolean enablePlaceholders, 是否使用佔位符
    int initialLoadSizeHint, //第一次加載多少數據
    int maxSize //最多保存多少數據
){...}
複製代碼
  • PagedListAdapter, 與RecyclerView.Adapter的使用區別不大,只是對getItemCount與getItem進行了重寫,由於它使用到了DiffUtil,避免對數據的無用更新。
class PagingAdapter : PagedListAdapter<ArticleModel, PagingVH>(diffCallbacks) {
 
    companion object {
        private val diffCallbacks = object : DiffUtil.ItemCallback<ArticleModel>() {

            override fun areItemsTheSame(oldItem: ArticleModel, newItem: ArticleModel): Boolean = oldItem.id == newItem.id
 
            override fun areContentsTheSame(oldItem: ArticleModel, newItem: ArticleModel): Boolean = oldItem == newItem

        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagingVH = PagingVH(R.layout.item_paging_article_layout, parent)
 
    override fun onBindViewHolder(holder: PagingVH, position: Int) = holder.bind(getItem(position))
}
複製代碼

這樣adapter也已經構建完成,最後一旦PagedList被觀察到,使用submitList傳入到adapter便可。ide

viewModel.userList.observe(this, Observer {
    adapter.submitList(it)
})
複製代碼

詳細使用能夠看看個人Demo,使用了mvvm + LiveData + Koin + 協程 + Retrofit。項目地址在最上面oop

問題

  • 爲何 DataSource.Factory 的create() 每次調用須要返回新對象 由於當 invalidate() 調用時 DataSource 的 mInvalid 會被設置爲 true,而後調用onInvalidated()
    //源碼
    @AnyThread
    public void invalidate() {
        if (mInvalid.compareAndSet(false, true)) {
            for (InvalidatedCallback callback : mOnInvalidatedCallbacks) {
                callback.onInvalidated();
            }
        }
    }
    複製代碼

LivePagedListBuilder 的create() 重寫了 DataSource.InvalidatedCallbackfetch

//LivePagedListBuilder 源碼    
    @AnyThread
    @NonNull
    @SuppressLint("RestrictedApi")
    private static <Key, Value> LiveData<PagedList<Value>> create(
            @Nullable final Key initialLoadKey,
            @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();
                        }
                    };

            @SuppressWarnings("unchecked") // for casting getLastKey to Key
            @Override
            protected PagedList<Value> compute() {
                @Nullable Key initializeKey = initialLoadKey;
                if (mList != null) {
                    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();
    }
}
複製代碼

能夠看到當 mList.isDetached() = true 時就會進入死循環。它的默認值是爲false 的。在do 裏面 mDataSource = dataSourceFactory.create(); mDataSource是從新獲取了的,而後經過 PagedList.Builder 去獲取新的 mList 數據。

@WorkerThread
    TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
            @NonNull Executor mainThreadExecutor,
            @NonNull Executor backgroundThreadExecutor,
            @Nullable BoundaryCallback<T> boundaryCallback,
            @NonNull Config config,
            int position) {
        super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor,
                boundaryCallback, config);
        mDataSource = dataSource;

        final int pageSize = mConfig.pageSize;
        mLastLoad = position;

        if (mDataSource.isInvalid()) {
            detach();
        } else {
            final int firstLoadSize =
                    (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize;

            final int idealStart = position - firstLoadSize / 2;
            final int roundedPageStart = Math.max(0, idealStart / pageSize * pageSize);

            mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize,
                    pageSize, mMainThreadExecutor, mReceiver);
        }
    }
複製代碼

獲取新數據前是會判斷 mDataSource.isInvalid() ,這時若是咱們從新的DataSource的create() 的方法返回的不是新對象,而是以前的對象,這個mDataSource已經調用invalidate() 所以就會走到 detach() 方法,不獲取新數據。

//源碼
    @SuppressWarnings("WeakerAccess")
    public void detach() {
        mDetached.set(true);
    }
複製代碼

能夠看到 detach() 會把mDetached 這位true,這就會致使mList.isDetached() 獲取的時候是true,LivePagedListBuilder 的 create() 方法就會死循環。

相關文章
相關標籤/搜索