当前位置:   article > 正文

android分页框架Paging源码分析_compute can happen only in 1 thread but no reason

compute can happen only in 1 thread but no reason to lock others

如果你还没用过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

  1. PagedList.Config config = new Builder()
  2. .setInitialLoadSizeHint(PAGE_SIZE)
  3. .setPageSize(PAGE_SIZE)
  4. .build();
  5. products = new LivePagedListBuilder(dataSourceFactory, config)
  6. .setInitialLoadKey(1).setFetchExecutor(AppsExecutor.networkIO())
  7. .build();
  8. return products;

 

第5步:实现DataSourceFactory工厂类创建加载数据的的DataSource(实现这个类就是为了加载数据,不管是从网络获取,还是从数据库、缓存、sd卡等获取)

  1. public class ProductDataFactory extends DataSource.Factory<Integer, Product> {
  2. public MutableLiveData<ProductDataSource> datasourceLiveData = new MutableLiveData<>();
  3. @Override
  4. public DataSource<Integer, Product> create() {
  5. ProductDataSource dataSource = new ProductDataSource(Service.get());
  6. datasourceLiveData.postValue(dataSource);
  7. return dataSource;
  8. }
  9. }

DataSource的实现类有ListDataSource、TiledDataSource、WrapperPositionalDataSource、LimitOffsetDataSource、PositionalDataSource、ItemKeyedDataSource、PageKeyedDataSource等你可以根据业务需要自己实现不同的类。我这里使用的是PageKeyedDataSource<Integer, Product>,需要你实现loadInitial、loadBefore、loadAfter三个方法,顾名思义loadInitial在第一次加载的时候调用也就是实现刷新在这个方法里实现,而加载更多在loadBeforeloadAfter方法中实现。

第6步:实现LiveData的的oberve的方法注册观察者回调

  1. mProductsViewModel.getProducts().observe(this, pagedList -> {
  2. mAdapter.submitList(pagedList);

第7步:在适配器中的onBindViewHolder方法中调用getItem(调用它就可以实现加载更多了)

ok,现在来分析源码是怎么进行的,首先明确几个重要类PagedListAdapter(负责适配数据,负责在getItem方法中实现继续加载),pageList类(持有数据的类),DataSource类(负责加载数据),LiveData(负责生命周期数据变化回调)。

咱们先从注册观察者跟进代码:

  1. public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
  2. if (owner.getLifecycle().getCurrentState() == DESTROYED) {
  3. // ignore
  4. return;
  5. }
  6. LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
  7. LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
  8. if (existing != null && existing.owner != wrapper.owner) {
  9. throw new IllegalArgumentException("Cannot add the same observer"
  10. + " with different lifecycles");
  11. }
  12. if (existing != null) {
  13. return;
  14. }
  15. owner.getLifecycle().addObserver(wrapper);
  16. }

这里的getLifecycle得到的是 LifecycleRegistry类,进入:

  1. public void addObserver(@NonNull LifecycleObserver observer) {
  2. State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
  3. ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
  4. ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
  5. if (previous != null) {
  6. return;
  7. }
  8. LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
  9. if (lifecycleOwner == null) {
  10. // it is null we should be destroyed. Fallback quickly
  11. return;
  12. }
  13. boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
  14. State targetState = calculateTargetState(observer);
  15. mAddingObserverCounter++;
  16. while ((statefulObserver.mState.compareTo(targetState) < 0
  17. && mObserverMap.contains(observer))) {
  18. pushParentState(statefulObserver.mState);
  19. statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
  20. popParentState();
  21. // mState / subling may have been changed recalculate
  22. targetState = calculateTargetState(observer);
  23. }
  24. if (!isReentrance) {
  25. // we do sync only on the top level.
  26. sync();
  27. }
  28. mAddingObserverCounter--;
  29. }

这段代码最重要的是statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState))这句话,添加观察者的时候,这个方法肯定会调用一次,最终会调用LifecycleBoundObserver的的onStateChanged方法

  1. public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
  2. if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
  3. removeObserver(mObserver);
  4. return;
  5. }
  6. activeStateChanged(shouldBeActive());
  7. }

然后调用到父类的activeStateChanged的方法

  1. void activeStateChanged(boolean newActive) {
  2. if (newActive == mActive) {
  3. return;
  4. }
  5. // immediately set active state, so we'd never dispatch anything to inactive
  6. // owner
  7. mActive = newActive;
  8. boolean wasInactive = LiveData.this.mActiveCount == 0;
  9. LiveData.this.mActiveCount += mActive ? 1 : -1;
  10. if (wasInactive && mActive) {
  11. onActive();
  12. }
  13. if (LiveData.this.mActiveCount == 0 && !mActive) {
  14. onInactive();
  15. }
  16. if (mActive) {
  17. dispatchingValue(this);
  18. }
  19. }
  20. }

终于到重点了,看第9行的判断,第一次调用的时候肯定为true,然后会调用onActive的方法,废话了这么多,总结起来就是LiveDataobserve方法调用的时候会执行一次LiveDataonActive方法,那么这个LiveData在哪创建的,现在回到第四步,LiveData通过LivePagedListBuilder创建的,来看一下它的build方法

  1. public LiveData<PagedList<Value>> build() {
  2. return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
  3. ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
  4. }
  1. private static <Key, Value> LiveData<PagedList<Value>> create(
  2. @Nullable final Key initialLoadKey,
  3. @NonNull final PagedList.Config config,
  4. @Nullable final PagedList.BoundaryCallback boundaryCallback,
  5. @NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
  6. @NonNull final Executor notifyExecutor,
  7. @NonNull final Executor fetchExecutor) {
  8. return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
  9. @Nullable
  10. private PagedList<Value> mList;
  11. @Nullable
  12. private DataSource<Key, Value> mDataSource;
  13. private final DataSource.InvalidatedCallback mCallback =
  14. new DataSource.InvalidatedCallback() {
  15. @Override
  16. public void onInvalidated() {
  17. invalidate();
  18. }
  19. };
  20. @Override
  21. protected PagedList<Value> compute() {
  22. @Nullable Key initializeKey = initialLoadKey;
  23. if (mList != null) {
  24. //noinspection unchecked
  25. initializeKey = (Key) mList.getLastKey();
  26. }
  27. do {
  28. if (mDataSource != null) {
  29. mDataSource.removeInvalidatedCallback(mCallback);
  30. }
  31. mDataSource = dataSourceFactory.create();
  32. mDataSource.addInvalidatedCallback(mCallback);
  33. mList = new PagedList.Builder<>(mDataSource, config)
  34. .setNotifyExecutor(notifyExecutor)
  35. .setFetchExecutor(fetchExecutor)
  36. .setBoundaryCallback(boundaryCallback)
  37. .setInitialKey(initializeKey)
  38. .build();
  39. } while (mList.isDetached());
  40. return mList;
  41. }
  42. }.getLiveData();
  43. }

最终通过ComputableLiveDatagetLiveData创建了LiveData,接着来看getLiveData方法,LiveData是在ComputableLiveData的构造方法中创建的

  1. public ComputableLiveData(@NonNull Executor executor) {
  2. mExecutor = executor;
  3. mLiveData = new LiveData<T>() {
  4. @Override
  5. protected void onActive() {
  6. mExecutor.execute(mRefreshRunnable);
  7. }
  8. };
  9. }

可以看到onActive方法在线程中执行了这面这段代码

  1. final Runnable mRefreshRunnable = new Runnable() {
  2. @WorkerThread
  3. @Override
  4. public void run() {
  5. boolean computed;
  6. do {
  7. computed = false;
  8. // compute can happen only in 1 thread but no reason to lock others.
  9. if (mComputing.compareAndSet(false, true)) {
  10. // as long as it is invalid, keep computing.
  11. try {
  12. T value = null;
  13. while (mInvalid.compareAndSet(true, false)) {
  14. computed = true;
  15. value = compute();
  16. }
  17. if (computed) {
  18. mLiveData.postValue(value);
  19. }
  20. } finally {
  21. // release compute lock
  22. mComputing.set(false);
  23. }
  24. }
  25. // check invalid after releasing compute lock to avoid the following scenario.
  26. // Thread A runs compute()
  27. // Thread A checks invalid, it is false
  28. // Main thread sets invalid to true
  29. // Thread B runs, fails to acquire compute lock and skips
  30. // Thread A releases compute lock
  31. // We've left invalid in set state. The check below recovers.
  32. } while (computed && mInvalid.get());
  33. }
  34. };

它首先调用了自己的compute方法,如下:

  1. @Nullable Key initializeKey = initialLoadKey;
  2. if (mList != null) {
  3. //noinspection unchecked
  4. initializeKey = (Key) mList.getLastKey();
  5. }
  6. do {
  7. if (mDataSource != null) {
  8. mDataSource.removeInvalidatedCallback(mCallback);
  9. }
  10. mDataSource = dataSourceFactory.create();
  11. mDataSource.addInvalidatedCallback(mCallback);
  12. mList = new PagedList.Builder<>(mDataSource, config)
  13. .setNotifyExecutor(notifyExecutor)
  14. .setFetchExecutor(fetchExecutor)
  15. .setBoundaryCallback(boundaryCallback)
  16. .setInitialKey(initializeKey)
  17. .build();
  18. } while (mList.isDetached());
  19. return mList;
  20. }

compute方法用来创建加载器Datasource以及数据源PageList(PagedListAdapter通过它获取数据),接着再看一下PageList的build方法

  1. public PagedList<Value> build() {
  2. // TODO: define defaults, once they can be used in module without android dependency
  3. if (mNotifyExecutor == null) {
  4. throw new IllegalArgumentException("MainThreadExecutor required");
  5. }
  6. if (mFetchExecutor == null) {
  7. throw new IllegalArgumentException("BackgroundThreadExecutor required");
  8. }
  9. //noinspection unchecked
  10. return PagedList.create(
  11. mDataSource,
  12. mNotifyExecutor,
  13. mFetchExecutor,
  14. mBoundaryCallback,
  15. mConfig,
  16. mInitialKey);
  17. }
  18. }
  19. /**

最终创建ContiguousPagedList

  1. ContiguousPagedList(
  2. @NonNull ContiguousDataSource<K, V> dataSource,
  3. @NonNull Executor mainThreadExecutor,
  4. @NonNull Executor backgroundThreadExecutor,
  5. @Nullable BoundaryCallback<V> boundaryCallback,
  6. @NonNull Config config,
  7. final @Nullable K key,
  8. int lastLoad) {
  9. super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
  10. boundaryCallback, config);
  11. mDataSource = dataSource;
  12. mLastLoad = lastLoad;
  13. if (mDataSource.isInvalid()) {
  14. detach();
  15. } else {
  16. mDataSource.dispatchLoadInitial(key,
  17. mConfig.initialLoadSizeHint,
  18. mConfig.pageSize,
  19. mConfig.enablePlaceholders,
  20. mMainThreadExecutor,
  21. mReceiver);
  22. }
  23. }

它的构造方法的倒数第三行里面实现了Datasource的的加载loadInitial方法在第一次加载数据(只有调用一次loadInitial,isInvalid()才返回true)。

 public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Product> callback) 

这个方法的最后一个参数用来将你加载的数据结果返回给DataSource,并最终PageList进行数据保存

  1. final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
  2. boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
  3. @NonNull PageResult.Receiver<Value> receiver) {
  4. LoadInitialCallbackImpl<Key, Value> callback =
  5. new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
  6. loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
  7. // If initialLoad's callback is not called within the body, we force any following calls
  8. // to post to the UI thread. This constructor may be run on a background thread, but
  9. // after constructor, mutation must happen on UI thread.
  10. callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
  11. }

可以看到这个回调参数就是LoadInitialCallbackImpl类:

  1. public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
  2. @Nullable Key nextPageKey) {
  3. if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
  4. mDataSource.initKeys(previousPageKey, nextPageKey);
  5. mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
  6. }
  7. }

这里又通过mCallbackHelper回调出去:

  1. void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
  2. Executor executor;
  3. synchronized (mSignalLock) {
  4. if (mHasSignalled) {
  5. throw new IllegalStateException(
  6. "callback.onResult already called, cannot call again.");
  7. }
  8. mHasSignalled = true;
  9. executor = mPostExecutor;
  10. }
  11. if (executor != null) {
  12. executor.execute(new Runnable() {
  13. @Override
  14. public void run() {
  15. mReceiver.onPageResult(mResultType, result);
  16. }
  17. });
  18. } else {
  19. mReceiver.onPageResult(mResultType, result);
  20. }
  21. }
  22. }

下面又通过 PageResult.Receiver<T>回调出去,它的实现还是在ContiguousPagedList这类里面

  1. @NonNull PageResult<V> pageResult) {
  2. if (pageResult.isInvalid()) {
  3. detach();
  4. return;
  5. }
  6. if (isDetached()) {
  7. // No op, have detached
  8. return;
  9. }
  10. List<V> page = pageResult.page;
  11. if (resultType == PageResult.INIT) {
  12. mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
  13. pageResult.positionOffset, ContiguousPagedList.this);
  14. if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
  15. // Because the ContiguousPagedList wasn't initialized with a last load position,
  16. // initialize it to the middle of the initial load
  17. mLastLoad =
  18. pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
  19. }
  20. } else if (resultType == PageResult.APPEND) {
  21. mStorage.appendPage(page, ContiguousPagedList.this);
  22. } else if (resultType == PageResult.PREPEND) {
  23. mStorage.prependPage(page, ContiguousPagedList.this);
  24. } else {
  25. throw new IllegalArgumentException("unexpected resultType " + resultType);
  26. }
  27. //省略若干行......
  28. }
  29. }
  30. };

这里做的工作只是判断是第一次加载,还是第二次存放数据(append),然后将数据保存到PagedStorage这个类中,现在再回到前面的compute中,它返回的TPageList<data>,而此时的PageList<data>已通过DataSource加载填充了数据,然后又调用了 LiveDatapostValue(value)方法,这个方法会触发(在activitty、frament处于生命周期活跃状态下)观察者的回调告诉观察者,我得数据改变了。你需要做点什么,也就是下面代码

  1. mProductsViewModel.getProducts().observe(this, new Observer<PagedList<Product>>() {
  2. @Override
  3. public void onChanged(@Nullable PagedList<Product> products) {
  4. mAdapter.submitList(products);
  5. }
  6. });

onChanged方法会被回调,这里就做了一件事,向适配器中赋值PageList,然后适配器通过PageList获取数据,从而RecyclerView有了第一页的数据,当然这里源码还有DiffUtil做了数据的分析,从而提高数据刷新速度。这就是第一页数据怎么加载出来的,那么后面数据怎么触发来的呢?

上面说过它是通过getItem数据来每次加载更多的,那么来看一下它的代码

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

继续跟入

  1. public T getItem(int index) {
  2. if (mPagedList == null) {
  3. if (mSnapshot == null) {
  4. throw new IndexOutOfBoundsException(
  5. "Item count is zero, getItem() call is invalid");
  6. } else {
  7. return mSnapshot.get(index);
  8. }
  9. }
  10. mPagedList.loadAround(index);
  11. return mPagedList.get(index);
  12. }

看到了什么,调用了pageList的loadAround方法,肯定是去加载数据吗。来看一下

  1. public void loadAround(int index) {
  2. mLastLoad = index + getPositionOffset();
  3. loadAroundInternal(index);
  4. mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
  5. mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
  6. /*
  7. * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
  8. * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
  9. * and accesses happen near the boundaries.
  10. *
  11. * Note: we post here, since RecyclerView may want to add items in response, and this
  12. * call occurs in PagedListAdapter bind.
  13. */
  14. tryDispatchBoundaryCallbacks(true);
  15. }

这里又调用了loadAroundInternal的方法,最终调用ContiguousPagedList类的loadAroundInternal方法

  1. protected void loadAroundInternal(int index) {
  2. int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
  3. int appendItems = index + mConfig.prefetchDistance
  4. - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
  5. mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
  6. if (mPrependItemsRequested > 0) {
  7. schedulePrepend();
  8. }
  9. mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
  10. if (mAppendItemsRequested > 0) {
  11. scheduleAppend();
  12. }
  13. }

传过来的参数是当前RecyclerViewitem的position,其中prefetchDistance为预加载的数量,mStorage.getLeadingNullCount()默认为0,从这个方法代码可以推测出如果当前的预加载减掉索引如果大于0的话,会调用DataSourceloadBefore方法,如果当前的预加载数量+索引-目前已经加载的数量的话>0的话调用DataSourceloadAfter方法,从而实现数据的分页加载,所以我们只要实现loadBefore或loadAfter一个方法就好了,最后是添加的话就会回调

  1. public void onPageAppended(int endPosition, int changedCount, int addedCount) {
  2. // consider whether to post more work, now that a page is fully appended
  3. mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
  4. mAppendWorkerRunning = false;
  5. if (mAppendItemsRequested > 0) {
  6. // not done appending, keep going
  7. scheduleAppend();
  8. }
  9. // finally dispatch callbacks, after append may have already been scheduled
  10. notifyChanged(endPosition, changedCount);
  11. notifyInserted(endPosition + changedCount, addedCount);
  12. }

方法来通知适配器数据改变了。

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/286489
推荐阅读
相关标签
  

闽ICP备14008679号