赞
踩
如果你还没用过Paging的话,很正常,毕竟现在用的并不多,做为android的开发人员分页的做法我们习惯写一个自定义控件,下拉的时候刷新(请求接口的页数置为1),上拉加载更多(请求接口数据页数实现累加)。然而,一种新的框架出来,我们应该尽可能的去了解它的架构,吸取人家谷歌工程师的代码精华。
好了,现在咱们先来了解一下怎么样用用这个分页库:
第一步:引入分页库
implementation "android.arch.paging:runtime:2.1.0"
既然是分页那么肯定用RecyclerView,那么
第二步:为RecyclerView设置适配器,实现适配器PagedListAdapter这个类,也就是我们常用的适配器的子类。(用法和普通适配器的用法一样)。
第三步:PagedListAdapter实现DiffUtil.ItemCallback,这个类为判断数据集合是否一样提供了分析,没用过DiffUtil的小伙伴请自行谷歌一下它的用法。
第四步:用LivePagedListBuilder类构建LiveData类(让数据和Activity或Frament的生命周期绑定)不了解的小伙伴可以看我的这篇博客https://blog.csdn.net/xiatiandefeiyu/article/details/78663612。
- PagedList.Config config = new Builder()
- .setInitialLoadSizeHint(PAGE_SIZE)
- .setPageSize(PAGE_SIZE)
- .build();
- products = new LivePagedListBuilder(dataSourceFactory, config)
- .setInitialLoadKey(1).setFetchExecutor(AppsExecutor.networkIO())
- .build();
-
- return products;
第5步:实现DataSourceFactory工厂类创建加载数据的的DataSource(实现这个类就是为了加载数据,不管是从网络获取,还是从数据库、缓存、sd卡等获取)
- public class ProductDataFactory extends DataSource.Factory<Integer, Product> {
-
- public MutableLiveData<ProductDataSource> datasourceLiveData = new MutableLiveData<>();
-
- @Override
- public DataSource<Integer, Product> create() {
-
- ProductDataSource dataSource = new ProductDataSource(Service.get());
- datasourceLiveData.postValue(dataSource);
- return dataSource;
- }
- }
DataSource的实现类有ListDataSource、TiledDataSource、WrapperPositionalDataSource、LimitOffsetDataSource、PositionalDataSource、ItemKeyedDataSource、PageKeyedDataSource等你可以根据业务需要自己实现不同的类。我这里使用的是PageKeyedDataSource<Integer, Product>,需要你实现loadInitial、loadBefore、loadAfter三个方法,顾名思义loadInitial在第一次加载的时候调用也就是实现刷新在这个方法里实现,而加载更多在loadBefore或loadAfter方法中实现。
第6步:实现LiveData的的oberve的方法注册观察者回调
- mProductsViewModel.getProducts().observe(this, pagedList -> {
- mAdapter.submitList(pagedList);
第7步:在适配器中的onBindViewHolder方法中调用getItem(调用它就可以实现加载更多了)
ok,现在来分析源码是怎么进行的,首先明确几个重要类PagedListAdapter(负责适配数据,负责在getItem方法中实现继续加载),pageList类(持有数据的类),DataSource类(负责加载数据),LiveData(负责生命周期数据变化回调)。
咱们先从注册观察者跟进代码:
- public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
- if (owner.getLifecycle().getCurrentState() == DESTROYED) {
- // ignore
- return;
- }
- LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
- LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
- if (existing != null && existing.owner != wrapper.owner) {
- throw new IllegalArgumentException("Cannot add the same observer"
- + " with different lifecycles");
- }
- if (existing != null) {
- return;
- }
- owner.getLifecycle().addObserver(wrapper);
- }
这里的getLifecycle得到的是 LifecycleRegistry类,进入:
- public void addObserver(@NonNull LifecycleObserver observer) {
- State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
- ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
- ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
-
- if (previous != null) {
- return;
- }
- LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
- if (lifecycleOwner == null) {
- // it is null we should be destroyed. Fallback quickly
- return;
- }
-
- boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
- State targetState = calculateTargetState(observer);
- mAddingObserverCounter++;
- while ((statefulObserver.mState.compareTo(targetState) < 0
- && mObserverMap.contains(observer))) {
- pushParentState(statefulObserver.mState);
- statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
- popParentState();
- // mState / subling may have been changed recalculate
- targetState = calculateTargetState(observer);
- }
-
- if (!isReentrance) {
- // we do sync only on the top level.
- sync();
- }
- mAddingObserverCounter--;
- }
这段代码最重要的是statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState))这句话,添加观察者的时候,这个方法肯定会调用一次,最终会调用LifecycleBoundObserver的的onStateChanged方法
- public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
- if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
- removeObserver(mObserver);
- return;
- }
- activeStateChanged(shouldBeActive());
- }
然后调用到父类的activeStateChanged的方法
- void activeStateChanged(boolean newActive) {
- if (newActive == mActive) {
- return;
- }
- // immediately set active state, so we'd never dispatch anything to inactive
- // owner
- mActive = newActive;
- boolean wasInactive = LiveData.this.mActiveCount == 0;
- LiveData.this.mActiveCount += mActive ? 1 : -1;
- if (wasInactive && mActive) {
- onActive();
- }
- if (LiveData.this.mActiveCount == 0 && !mActive) {
- onInactive();
- }
- if (mActive) {
- dispatchingValue(this);
- }
- }
- }
终于到重点了,看第9行的判断,第一次调用的时候肯定为true,然后会调用onActive的方法,废话了这么多,总结起来就是LiveData的observe方法调用的时候会执行一次LiveData的onActive方法,那么这个LiveData在哪创建的,现在回到第四步,LiveData通过LivePagedListBuilder创建的,来看一下它的build方法
- public LiveData<PagedList<Value>> build() {
- return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
- ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
- }
- 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();
- }
- };
-
- @Override
- protected PagedList<Value> compute() {
- @Nullable Key initializeKey = initialLoadKey;
- if (mList != null) {
- //noinspection unchecked
- 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();
- }
最终通过ComputableLiveData的getLiveData创建了LiveData,接着来看getLiveData方法,LiveData是在ComputableLiveData的构造方法中创建的
- public ComputableLiveData(@NonNull Executor executor) {
- mExecutor = executor;
- mLiveData = new LiveData<T>() {
- @Override
- protected void onActive() {
- mExecutor.execute(mRefreshRunnable);
- }
- };
- }
可以看到onActive方法在线程中执行了这面这段代码
- final Runnable mRefreshRunnable = new Runnable() {
- @WorkerThread
- @Override
- public void run() {
- boolean computed;
- do {
- computed = false;
- // compute can happen only in 1 thread but no reason to lock others.
- 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);
- }
- }
- // check invalid after releasing compute lock to avoid the following scenario.
- // Thread A runs compute()
- // Thread A checks invalid, it is false
- // Main thread sets invalid to true
- // Thread B runs, fails to acquire compute lock and skips
- // Thread A releases compute lock
- // We've left invalid in set state. The check below recovers.
- } while (computed && mInvalid.get());
- }
- };
它首先调用了自己的compute方法,如下:
- @Nullable Key initializeKey = initialLoadKey;
- if (mList != null) {
- //noinspection unchecked
- 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;
- }
compute方法用来创建加载器Datasource以及数据源PageList(PagedListAdapter通过它获取数据),接着再看一下PageList的build方法
- public PagedList<Value> build() {
- // TODO: define defaults, once they can be used in module without android dependency
- if (mNotifyExecutor == null) {
- throw new IllegalArgumentException("MainThreadExecutor required");
- }
- if (mFetchExecutor == null) {
- throw new IllegalArgumentException("BackgroundThreadExecutor required");
- }
-
- //noinspection unchecked
- return PagedList.create(
- mDataSource,
- mNotifyExecutor,
- mFetchExecutor,
- mBoundaryCallback,
- mConfig,
- mInitialKey);
- }
- }
-
- /**
最终创建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);
- }
- }
它的构造方法的倒数第三行里面实现了Datasource的的加载loadInitial方法在第一次加载数据(只有调用一次loadInitial,isInvalid()才返回true)。
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Product> callback)
这个方法的最后一个参数用来将你加载的数据结果返回给DataSource,并最终PageList进行数据保存
- 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);
-
- // If initialLoad's callback is not called within the body, we force any following calls
- // to post to the UI thread. This constructor may be run on a background thread, but
- // after constructor, mutation must happen on UI thread.
- callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
- }
可以看到这个回调参数就是LoadInitialCallbackImpl类:
- 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));
- }
- }
这里又通过mCallbackHelper回调出去:
- void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
- Executor executor;
- 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);
- }
- }
- }
下面又通过 PageResult.Receiver<T>回调出去,它的实现还是在ContiguousPagedList这类里面
- @NonNull PageResult<V> pageResult) {
- if (pageResult.isInvalid()) {
- detach();
- return;
- }
- if (isDetached()) {
- // No op, have detached
- return;
- }
-
- List<V> page = pageResult.page;
- if (resultType == PageResult.INIT) {
- mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
- pageResult.positionOffset, ContiguousPagedList.this);
- if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
- // Because the ContiguousPagedList wasn't initialized with a last load position,
- // initialize it to the middle of the initial load
- mLastLoad =
- pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
- }
- } else if (resultType == PageResult.APPEND) {
- mStorage.appendPage(page, ContiguousPagedList.this);
- } else if (resultType == PageResult.PREPEND) {
- mStorage.prependPage(page, ContiguousPagedList.this);
- } else {
- throw new IllegalArgumentException("unexpected resultType " + resultType);
- }
- //省略若干行......
- }
- }
- };
这里做的工作只是判断是第一次加载,还是第二次存放数据(append),然后将数据保存到PagedStorage这个类中,现在再回到前面的compute中,它返回的T是PageList<data>,而此时的PageList<data>已通过DataSource加载填充了数据,然后又调用了 LiveData的postValue(value)方法,这个方法会触发(在activitty、frament处于生命周期活跃状态下)观察者的回调告诉观察者,我得数据改变了。你需要做点什么,也就是下面代码
- mProductsViewModel.getProducts().observe(this, new Observer<PagedList<Product>>() {
- @Override
- public void onChanged(@Nullable PagedList<Product> products) {
- mAdapter.submitList(products);
- }
- });
的onChanged方法会被回调,这里就做了一件事,向适配器中赋值PageList,然后适配器通过PageList获取数据,从而RecyclerView有了第一页的数据,当然这里源码还有DiffUtil做了数据的分析,从而提高数据刷新速度。这就是第一页数据怎么加载出来的,那么后面数据怎么触发来的呢?
上面说过它是通过getItem数据来每次加载更多的,那么来看一下它的代码
- protected T getItem(int position) {
- return mDiffer.getItem(position);
- }
继续跟入
- 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);
- }
看到了什么,调用了pageList的loadAround方法,肯定是去加载数据吗。来看一下
- 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);
- }
这里又调用了loadAroundInternal的方法,最终调用ContiguousPagedList类的loadAroundInternal方法
- 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();
- }
- }
传过来的参数是当前RecyclerView的item的position,其中prefetchDistance为预加载的数量,mStorage.getLeadingNullCount()默认为0,从这个方法代码可以推测出如果当前的预加载减掉索引如果大于0的话,会调用DataSource的loadBefore方法,如果当前的预加载数量+索引-目前已经加载的数量的话>0的话调用DataSource的loadAfter方法,从而实现数据的分页加载,所以我们只要实现loadBefore或loadAfter一个方法就好了,最后是添加的话就会回调
- 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);
- }
方法来通知适配器数据改变了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。