Android Jetpack之Paging

Jetpack中的Paging組件可以輕鬆的給RecyclerView增加分頁加載的功能,通過預加載實現無限滑動的效果。

先說一下這無限滑動效果,項目中的分頁加載一般分兩大種情況:

  1. 一種是滑到底部上拉鬆手後加載或者滑到底部後自動顯示一個加載動畫加載。
  2. 一種是當還沒滑動到底部的時候就開始加載了,當滑到底部的時候數據可能已經加載完成並續上了,這樣就有一種無限滑動的感覺,Paging默認就是這種模式。

1 簡單例子

下面先寫個簡單的小例子看看怎麼用,然後在去看原理,使用鴻神的玩安卓網站的首頁內容的api來完成這個小例子

引入最新的依賴:

implementation "androidx.paging:paging-runtime:2.1.0"

想要完成一個分頁加載的列表,一般寫三個部分,1. 數據部分 2.adapter部分 3.activity/fragment

Paging使用的時候當然要配合JetPack組件中的其他組件來使用了,用起來更酸爽,單獨使用它沒有意義。這裏就配合LiveData和ViewModel

1.1 數據來源

public class PagingViewModel extends ViewModel {

    private static final String TAG = PagingViewModel.class.getSimpleName();

    private LiveData<PagedList<ArticleResponse.DataBean.DatasBean>> articleRes = null;
    private ArticleDataSource mDataSource;
    //是否有數據
    private MutableLiveData<Boolean> boundaryPageData = new MutableLiveData<>();

    public LiveData<PagedList<ArticleResponse.DataBean.DatasBean>> getArticleLiveData() {
        if (articleRes == null) {
            PagedList.Config config = new PagedList.Config.Builder()
                    .setPageSize(20)
                    .setInitialLoadSizeHint(22)
                    .build();
            articleRes = new LivePagedListBuilder<Integer, ArticleResponse.DataBean.DatasBean>(mFactory, config)
                    .setBoundaryCallback(mBoundaryCallback)
                    .build();
        }
        return articleRes;
    }

    private DataSource.Factory mFactory = new DataSource.Factory() {
        @NonNull
        @Override
        public DataSource create() {
            if (mDataSource == null) {
                mDataSource = new ArticleDataSource();
            }
            return mDataSource;
        }
    };

    //監聽數據邊界
    private PagedList.BoundaryCallback mBoundaryCallback = new PagedList.BoundaryCallback<ArticleResponse.DataBean.DatasBean>() {
        @Override
        public void onZeroItemsLoaded() {
            super.onZeroItemsLoaded();
            //初始化數據
            boundaryPageData.postValue(false);
        }

        @Override
        public void onItemAtFrontLoaded(@NonNull ArticleResponse.DataBean.DatasBean itemAtFront) {
            super.onItemAtFrontLoaded(itemAtFront);
            //正在添加數據
            boundaryPageData.postValue(true);
        }

        @Override
        public void onItemAtEndLoaded(@NonNull ArticleResponse.DataBean.DatasBean itemAtEnd) {
            super.onItemAtEndLoaded(itemAtEnd);
            //沒有數據加載了
            boundaryPageData.postValue(false);
        }
    };

    public ArticleDataSource getDataSource() {
        return mDataSource;
    }

    public MutableLiveData<Boolean> getBoundaryPageData() {
        return boundaryPageData;
    }

    public void loadData(int currentPage, PageKeyedDataSource.LoadInitialCallback<Integer, ArticleResponse.DataBean.DatasBean> initialCallback
            , PageKeyedDataSource.LoadCallback<Integer, ArticleResponse.DataBean.DatasBean> callback) {
        String url = "https://www.wanandroid.com/article/list/" + currentPage + "/json";
        OkGo.<String>get(url)
                .execute(new StringCallback() {
                    @Override
                    public void onSuccess(Response<String> response) {
                        Gson gson = new Gson();
                        ArticleResponse articleResponse = gson.fromJson(response.body(), ArticleResponse.class);
                        if (initialCallback != null) {
                            initialCallback.onResult(articleResponse.getData().getDatas(), -1, 0);
                        } else {
                            callback.onResult(articleResponse.getData().getDatas(), currentPage);
                        }
                        boundaryPageData.postValue(articleResponse.getData().getDatas().size() <= 0);
                    }
                });
    }

    public class ArticleDataSource extends PageKeyedDataSource<Integer, ArticleResponse.DataBean.DatasBean> {
        @Override
        public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, ArticleResponse.DataBean.DatasBean> callback) {
            //開始加載數據
            loadData(0, callback, null);
            Log.d(TAG, "loadInitial");
        }

        @Override
        public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, ArticleResponse.DataBean.DatasBean> callback) {
            //往前加載數據
        }

        @Override
        public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, ArticleResponse.DataBean.DatasBean> callback) {
            //往後加載數據
            loadData(params.key + 1, null, callback);
            Log.d(TAG, "loadAfter");
        }
    }
}

前面的代碼主要就是幹了兩件事:

  1. 如何創建一個數據集
  2. 如何請求網絡獲取數據給數據集

數據集使用LiveData觀察,使用PagedList保存,PagedList顧名思義頁面集合或者說是數據集合,配合LiveData觀察者可以很方便的增加數據。

創建數據集使用LivePagedListBuilder來創建,它需要兩個參數數據工廠和分頁配置

分頁配置:通過PagedList.Config類來實現,可以通過構建者來給它設置不同的屬性

  • setPageSize() 設置每次分頁加載的數量
  • setInitialLoadSizeHint() 設置初始化數據的時候加載數據的數量
  • setPrefetchDistance() 指定提前預加載的時機(跟最後一條的距離)
  • setMaxSize() 指定數據源最大可以加載的數量
  • setEnablePlaceholders() 是否使用佔位符,配合setMaxSize()使用,未加載出來的部分使用佔位符代替

數據工廠:DataSource.Factory主要是用來創建數據源 DataSource ,Paging框架主要提供了3種數據源類型。

  • PageKeyedDataSource :主要用於使用頁碼分頁的情況每加載一次page++,上面的小demo就是使用的這種數據源
  • ItemKeyedDataSource:下一頁的加載需要前面的某個item的信息來加載。比如傳入某個item的id,通過這個id來獲取該item後面的數據
  • PositionalDataSource :數據源固定,通過特定位置來加載數據

demo中用的是PageKeyedDataSource,它是個抽象類,有三個抽象方法

  1. loadInitial:初始化第一頁數據
  2. loadBefore:往前分頁加載
  3. loadAfter:往後分頁加載

在loadInitial方法中開始第一頁的數據,在loadAfter中開始往後分頁加載,數據加載完成後保存在PagedList中

上面代碼中還設置了一個PagedList.BoundaryCallback,它是數據加載的邊界回調,它有三個回到方法分別是初始化數據,正在添加數據,數據沒有了加載結束。

1.2 adapter

public class PagingAdapter extends PagedListAdapter<ArticleResponse.DataBean.DatasBean, PagingAdapter.ViewHolder> {

    protected PagingAdapter() {
        super(new DiffUtil.ItemCallback<ArticleResponse.DataBean.DatasBean>() {
            @Override
            public boolean areItemsTheSame(@NonNull ArticleResponse.DataBean.DatasBean oldItem, @NonNull ArticleResponse.DataBean.DatasBean newItem) {
                return oldItem.getId() == newItem.getId();
            }

            @Override
            public boolean areContentsTheSame(@NonNull ArticleResponse.DataBean.DatasBean oldItem, @NonNull ArticleResponse.DataBean.DatasBean newItem) {
                return oldItem.equals(newItem);
            }
        });
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item,parent,false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        ArticleResponse.DataBean.DatasBean bean = getItem(position);
          holder.tvname.setText(bean.getTitle());
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvname;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            tvname = itemView.findViewById(R.id.tvname);
        }
    }
}

使用Paging框架就必須要繼承PagedListAdapter了,它強制要求傳入一個DiffUtil.ItemCallback,它是用來對新舊數據之間進行差分計算,這樣Paging框架就有差量更新的能力了。DiffUtil.ItemCallback的兩個實現方法中我們需要自己定義一下差分規則。

剩下的就是跟正常寫一個RecyclerView.Adapter一樣了。onBindViewHolder中通過PagedListAdapter提供的getItem(position);方法拿到當前item的數據對象

1.3 activity中使用

public class PagingActivity extends AppCompatActivity {
    private SmartRefreshLayout refreshLayout;
    private RecyclerView recyclerView;
    private PagingAdapter mAdapter;
    private PagingViewModel viewModel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_paging);
        refreshLayout = findViewById(R.id.refreshLayout);
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));

        mAdapter  = new PagingAdapter();
        recyclerView.setAdapter(mAdapter);

        viewModel = new ViewModelProvider(this).get(PagingViewModel.class);
        viewModel.getArticleLiveData().observe(this, new Observer<PagedList<ArticleResponse.DataBean.DatasBean>>() {
            @Override
            public void onChanged(PagedList<ArticleResponse.DataBean.DatasBean> datasBeans) {
                   mAdapter.submitList(datasBeans);
            }
        });
        viewModel.getBoundaryPageData().observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean haData) {
                if(!haData){
                    refreshLayout.finishLoadMore();
                    refreshLayout.finishRefresh();
                }
            }
        });
    }
}

通過viewModel.getArticleLiveData()方法拿到前面定義的LiveData然後調用它的observe方法,就能監聽到數據的改變了,在其回調中調用adapter的submitList方法將返回的數據提交到adapter中,就能自動刷新加載了。

OK 到這裏一個簡單的Paging使用就完成了下面看看效果如何,這個接口每次默認返回20條數據。

在這裏插入圖片描述

2 原理分析

知道怎麼用了只是第一步,還需要去看看它的源碼到底是怎麼個流程,畢竟這只是一個通用框架,跟業務結合的時候保不準哪裏就不吻合,需要自己改造一下。或者用的時候發現該框架有什麼地方不合理,比如Paging框架如果有一次分頁失敗,就不繼續分頁了,如果是接口的問題,此時我們肯定希望能繼續分頁。

2.1 如何初始化第一次加載的

下面看看主幹流程,從Activity中開始看源碼:

前面我們知道,調用 viewModel.getArticleLiveData()方法拿到一個LiveData對象,調用其observe方法就能監聽到數據的改變了。這個LiveData對象是通過LivePagedListBuilder這個類build出來的。那就從這個build方法開始。

public LiveData<PagedList<Value>> build() {
    return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
        ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
    }

調用了create方法,傳入的參數我們也都很熟悉,有我們自定義的PagedList的配置,數據源工廠,還有線程池等

   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();
    }

new 了一個ComputableLiveData,最後調用其getLiveData()返回最終的LiveData對象。

public abstract class ComputableLiveData<T> {
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final Executor mExecutor;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final LiveData<T> mLiveData;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final AtomicBoolean mInvalid = new AtomicBoolean(true);
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final AtomicBoolean mComputing = new AtomicBoolean(false);
    @SuppressWarnings("WeakerAccess")
    public ComputableLiveData() {
        this(ArchTaskExecutor.getIOThreadExecutor());
    }

    @SuppressWarnings("WeakerAccess")
    public ComputableLiveData(@NonNull Executor executor) {
        mExecutor = executor;
        mLiveData = new LiveData<T>() {
            @Override
            protected void onActive() {
                mExecutor.execute(mRefreshRunnable);
            }
        };
    }

    @NonNull
    public LiveData<T> getLiveData() {
        return mLiveData;
    }

    @VisibleForTesting
    final Runnable mRefreshRunnable = new Runnable() {
        @WorkerThread
        @Override
        public void run() {
            boolean computed;
            do {
                computed = false;
                // 計算智能在一個線程池中進行
                if (mComputing.compareAndSet(false, true)) {
                    // as long as it is invalid, keep computing.
                    try {
                        T value = null;
                        while (mInvalid.compareAndSet(true, false)) {
                            computed = true;
                            value = compute();
                        }
                        if (computed) {
                            mLiveData.postValue(value);
                        }
                    } finally {
                        // release compute lock
                        mComputing.set(false);
                    }
                }
            } while (computed && mInvalid.get());
        }
    };

    @VisibleForTesting
    final Runnable mInvalidationRunnable = new Runnable() {
        @MainThread
        @Override
        public void run() {
            boolean isActive = mLiveData.hasActiveObservers();
            if (mInvalid.compareAndSet(false, true)) {
                if (isActive) {
                    mExecutor.execute(mRefreshRunnable);
                }
            }
        }
    };
    //該方法會重新觸發compute()方法
    public void invalidate() {
        ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
    }

    @WorkerThread
    protected abstract T compute();
}

可以看到這個ComputableLiveData類並不是一個真正的LiveData,而是包裝了一個LiveData,在其構造方法中初始了成員變量mLiveData,並重寫了其onActive()方法。

這個地方需要對LiveData有一點了解,可以看之前的文章Android Jetpack之LiveData。當LiveData對象處於活動狀態的時候會回調onActive()方法。LiveData是生命週期感知組件,它處於活躍狀態,說明當前Activity或者Fragment處於活躍狀態,也就是肯定在onStart()生命週期後面。所以這裏會立馬執行onActive()方法。

手動調用invalidate()方法,可以手動觸發onActive方法中的邏輯。

onActive()方法中調用了mExecutor.execute(mRefreshRunnable);去線程池中執行mRefreshRunnable這個Runnable對象,run方法中執行了compute()這個抽象方法,這個方法的唯一實現是在前面new ComputableLiveData的時候實現的。在複製過來看一下

   protected PagedList<Value> compute() {
                @Nullable Key initializeKey = initialLoadKey;
                if (mList != null) {
                    initializeKey = (Key) mList.getLastKey();
                }

                do {
                    if (mDataSource != null) {
                        mDataSource.removeInvalidatedCallback(mCallback);
                    }
                 //拿到我們傳入的DataSource
                mDataSource = dataSourceFactory.create();
                //給DataSource註冊一個回調,用來監聽DataSource被置爲無效事件 
                mDataSource.addInvalidatedCallback(mCallback);

                mList = new PagedList.Builder<>(mDataSource, config)
                            .setNotifyExecutor(notifyExecutor)
                            .setFetchExecutor(fetchExecutor)
                            .setBoundaryCallback(boundaryCallback)
                            .setInitialKey(initializeKey)
                            .build();
                } while (mList.isDetached());
                return mList;
            }

拿到我們傳入的DataSource,然後給它添加一個回調用來監聽DataSource被置爲無效事件。堅挺到無效事件之後會重新觸發compute()方法。最後通過PagedList.Builder構建一個PagedList對象。

跟進它的build方法

    public PagedList<Value> build() {
            if (mNotifyExecutor == null) {
                throw new IllegalArgumentException("MainThreadExecutor required");
            }
            if (mFetchExecutor == null) {
                throw new IllegalArgumentException("BackgroundThreadExecutor required");
            }
            return PagedList.create(mDataSource,mNotifyExecutor,mFetchExecutor, mBoundaryCallback,mConfig,mInitialKey);
        }

先判斷傳入的線程池不能爲null,然後PagedList.create方法創建

    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 = (Integer) 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);
        }
    }

這段代碼就是判斷該創建哪種DataSource的PagedList。前面我們知道系統默認有三種DataSource分別是PageKeyedDataSource、ItemKeyedDataSource、PositionalDataSource。

dataSource.isContiguous()這個判斷PageKeyedDataSource、ItemKeyedDataSource返回的是true,PositionalDataSource返回的是false,前面demo中使用的是PageKeyedDataSource所以這裏返回true,最終進入到if判斷中創建了一個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);
        }
        mShouldTrim = mDataSource.supportsPageDropping()
                && mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED;
    }

如果mDataSource是無效的就執行detach()方法,detach方法註釋上說了,該方法嘗試把DataSource從PagedList中分離,並且嘗試不在加載數據。當數據無法加載的時候mDataSource.isInvalid()就會被置爲true,也就不會再繼續幫我們分頁了

網絡數據加載異常是經常發生的事情,那怎麼解決呢,有一種辦法就是從當前adapter中拿出所有的PagedList數據和配置,然後手動觸發請求網絡的代碼,拿到數據之後,將連個數據合併,並創建一個新的PagedList,最後調用submitList方法。

如果DataSource是有效的就執行mDataSource.dispatchLoadInitial方法來分發原始加載,然後就來到PageKeyedDataSource類中的dispatchLoadInitial方法,記住最後一個參數mReceiver,加載完數據後就使用它將數據回調回來。

  @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);

        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
    }

創建了一個LoadInitialCallbackImpl對象然後調用loadInitial方法來加載初始化的數據

這裏loadInitial是一個抽象方法,最終就調用了我們最開始第一步中自定義的ArticleDataSource中的loadInitial開始加載原始數據

數據加載完成之後,通過initialCallback.onResult方法將數據回調,也就是到了LoadInitialCallbackImpl類中的onResult方法

    static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {
        final LoadCallbackHelper<Value> mCallbackHelper;
        private final PageKeyedDataSource<Key, Value> mDataSource;
        private final boolean mCountingEnabled;
        LoadInitialCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
                boolean countingEnabled, @NonNull PageResult.Receiver<Value> receiver) {
            mCallbackHelper = new LoadCallbackHelper<>(
                    dataSource, PageResult.INIT, null, receiver);
            mDataSource = dataSource;
            mCountingEnabled = countingEnabled;
        }

        @Override
        public void onResult(@NonNull List<Value> data, int position, int totalCount,
                @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);

                // setup keys before dispatching data, so guaranteed to be ready
                mDataSource.initKeys(previousPageKey, nextPageKey);

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

        @Override
        public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
                @Nullable Key nextPageKey) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                mDataSource.initKeys(previousPageKey, nextPageKey);
                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
            }
        }
    }

onResult方法還有個重載的方法,還有各種判斷,不過最終都會走到mCallbackHelper.dispatchResultToReceiver方法,分發結果。

  void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
            Executor executor;
            //一個callback只能調用一次
            synchronized (mSignalLock) {
                if (mHasSignalled) {
                    throw new IllegalStateException(
                            "callback.onResult already called, cannot call again.");
                }
                mHasSignalled = true;
                executor = mPostExecutor;
            }

            if (executor != null) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        mReceiver.onPageResult(mResultType, result);
                    }
                });
            } else {
                mReceiver.onPageResult(mResultType, result);
            }
        }

這裏可以看到最終通過mReceiver的onPageResult方法把數據回調會去,mReceiver就是前面調用dispatchLoadInitial方法的時候傳過來的。

onPageResult方法中把回調回來的數據保存在PagedStorage中的成員變量private final ArrayList<List<T>> mPages;中完畢。

2.2 如何觸發分頁加載的

分頁加載的邏輯就應該在跟RecyclerView有關的adapter中觸發了,在adapter的onBindViewHolder方法中,我們使用getItem(position)方法獲取當前item的對象。這個方法時PagedListAdapter提供的。

   protected T getItem(int position) {
        return mDiffer.getItem(position);
    }

mDiffer是AsyncPagedListDiffer的對象,所以調用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);
    }

如果mPagedList不爲null,就調用mPagedList.loadAround方法

    public void loadAround(int index) {
        if (index < 0 || index >= size()) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
        }

        mLastLoad = index + getPositionOffset();
        loadAroundInternal(index);

        mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
        mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
        
        tryDispatchBoundaryCallbacks(true);
    }

又調用了loadAroundInternal方法,它是一個抽象方法,最終調用到PagedList的子類ContiguousPagedList中的loadAroundInternal方法

 protected void loadAroundInternal(int index) {
        int prependItems = getPrependItemsRequested(mConfig.prefetchDistance, index,
                mStorage.getLeadingNullCount());
        int appendItems = getAppendItemsRequested(mConfig.prefetchDistance, index,
                mStorage.getLeadingNullCount() + mStorage.getStorageCount());

        mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
        //如果向前預加載是數量大於0
        if (mPrependItemsRequested > 0) {
            schedulePrepend();
        }
        mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
        //如果向後預加載的數量大於0
        if (mAppendItemsRequested > 0) {
            scheduleAppend();
        }
    }

如果向前預加載是數量大於0,就調用schedulePrepend();方法,該方法最終會調用到DataSource中的loadBefore方法。

 private void schedulePrepend() {
        ......
        mBackgroundThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (isDetached()) {
                    return;
                }
                if (mDataSource.isInvalid()) {
                    detach();
                } else {
                    mDataSource.dispatchLoadBefore(position, item, mConfig.pageSize,
                            mMainThreadExecutor, mReceiver);
                }
            }
        });
    }
     @Override
    final void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,int pageSize, @NonNull Executor mainThreadExecutor,@NonNull PageResult.Receiver<Value> receiver) {
        @Nullable Key key = getPreviousKey();
        if (key != null) {
            loadBefore(new LoadParams<>(key, pageSize),
                    new LoadCallbackImpl<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
        } else {
            receiver.onPageResult(PageResult.PREPEND, PageResult.<Value>getEmptyResult());
        }
    }

如果是向後預加載會調用scheduleAppend();方法,該方法最終會調用到DataSource中的loadAfter方法,最終觸發分頁加載

   private void scheduleAppend() {
       ......
        mBackgroundThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (isDetached()) {
                    return;
                }
                if (mDataSource.isInvalid()) {
                    detach();
                } else {
                    mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
                            mMainThreadExecutor, mReceiver);
                }
            }
        });
    }
    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));
        } else {
            receiver.onPageResult(PageResult.APPEND, PageResult.<Value>getEmptyResult());
        }
    }

調用loadAfter的時候傳入了一個回調的實現類LoadCallbackImpl,當loadAfter中的數據加載完成之後,會通過這個類把結果回調回來

   static class LoadCallbackImpl<Key, Value> extends LoadCallback<Key, Value> {
        final LoadCallbackHelper<Value> mCallbackHelper;
        private final PageKeyedDataSource<Key, Value> mDataSource;
        LoadCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
                @PageResult.ResultType int type, @Nullable Executor mainThreadExecutor,
                @NonNull PageResult.Receiver<Value> receiver) {
            mCallbackHelper = new LoadCallbackHelper<>(
                    dataSource, type, mainThreadExecutor, receiver);
            mDataSource = dataSource;
        }

        @Override
        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));
            }
        }
    }

最終會跟初始化數據一樣,調用mCallbackHelper.dispatchResultToReceiver方法,將數據保存在PagedList的成員變量PagedStorage中。

到這裏初始化的流程和分頁加載的流程就都瞭解了,本文是以PageKeyedDataSource爲例往下跟的,其他兩個ItemKeyedDataSource、PositionalDataSource原理基本一樣。

2.3 如何刷新RecyclerView

這個要從Activity中mAdapter.submitList(datasBeans);開始看了。

 public void submitList(@Nullable PagedList<T> pagedList) {
        mDiffer.submitList(pagedList);
    }

也是調用了AsyncPagedListDiffer中的submitList。其實可以看到PagedListAdapter其實只是繼承了RecyclerView.Adapter,它內部跟Paging有關的操作都交給了AsyncPagedListDiffer這個類取做了。

 public void submitList(@Nullable final PagedList<T> pagedList) {
        submitList(pagedList, null);
    }
 public void submitList(@Nullable final PagedList<T> pagedList,
         @Nullable final Runnable commitCallback) {
          
          ......
          
            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());

            onCurrentListChanged(null, pagedList, commitCallback);
            return;
        }
          if (mPagedList != null) {
            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) {
                            latchPagedList(pagedList, newSnapshot, result,
                                    oldSnapshot.mLastLoad, commitCallback);
                        }
                    }
                });
              
            }
        });
    }

如果mPagedList爲null或者他的快照爲null,說明這是第一次初始化數據,調用mUpdateCallback.onInserted(0, pagedList.size());方法將數據插入到列表中。

這個mUpdateCallback是在AsyncPagedListDiffer構造方法中初始化的,是AdapterListUpdateCallback類型。AdapterListUpdateCallback是RecyclerView包中的類,內部調用了RecyclerView.Adapter的各種notify的方法來刷新界面。

public final class AdapterListUpdateCallback implements ListUpdateCallback {
    @NonNull
    private final RecyclerView.Adapter mAdapter;

    public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) {
        mAdapter = adapter;
    }

    @Override
    public void onInserted(int position, int count) {
        mAdapter.notifyItemRangeInserted(position, count);
    }

    @Override
    public void onRemoved(int position, int count) {
        mAdapter.notifyItemRangeRemoved(position, count);
    }

    @Override
    public void onMoved(int fromPosition, int toPosition) {
        mAdapter.notifyItemMoved(fromPosition, toPosition);
    }

    @Override
    public void onChanged(int position, int count, Object payload) {
        mAdapter.notifyItemRangeChanged(position, count, payload);
    }
}

如果mPagedList不爲null,說明是分頁加載數據,調用PagedStorageDiffHelper.computeDiff方法計算出差分結果後,切換到主線程調用latchPagedList方法刷新

void latchPagedList(
            @NonNull PagedList<T> newList,
            @NonNull PagedList<T> diffSnapshot,
            @NonNull DiffUtil.DiffResult diffResult,
            int lastAccessIndex,
            @Nullable Runnable commitCallback) {
               
               ......
                PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
                previousSnapshot.mStorage, newList.mStorage, diffResult);
                
               ......
            }

最後也是通過mUpdateCallback的各種回調來最後更新列表的。

總結

總結一下Paging框架的工作流程:

初始化數據:

  • LivePagedListBuilder#build()->LivePagedListBuilder#create創建一個ComputableLiveData最後返回一個LiveData數據觀察者
  • LiveData的onActive()方法會根據activity/fragment的生命週期自動觸發,加載的邏輯也在這開始
  • ComputableLiveData#compute-PagedList#build->PagedList#create。根據我們傳入的不同的DataSource返回不同的PagedList。ContiguousPagedList或者TiledPagedList
  • 在創建出來的ContiguousPagedList或者TiledPagedList的構造方法中進行初始數據的分發和加載
  • 數據加載完成之後,通過LoadInitialCallbackImpl#onResult回調回來最終保存在PagedList的成員變量PagedStorage中

分頁加載數據:

  • PagedListAdapter#getItem->AsyncPagedListDiffer#getItem->PagedList#loadAround->PagedList#loadAroundInternal。loadAroundInternal是抽象方法
  • 在子類ContiguousPagedList或者TiledPagedList的loadAroundInternal方法中計算出需要向前加載還是向後加載,並觸發相關方法
  • 數據加載完成之後,通過LoadCallbackImpl#onResult方法回調結果,最終保存在PagedList的成員變量PagedStorage中

刷新列表

  • mAdapter#submitList;->AsyncPagedListDiffer#submitList
  • AdapterListUpdateCallback#onInsertedAdapterListUpdateCallback#onRemoved
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章