当前位置:   article > 正文

android架构组件之paging源码解析_android paging pagepresenter源码

android paging pagepresenter源码

RecyclerView使用paging就是多了对数据的拉取,使得RecyclerView的数据和显示更加的解耦,RecyclerView对paging的使用多了如下几步:

  1. instance = CustomAdapter.getInstance(this);
  2. factory = new CustomPageDataSourceFactory<>();
  3. build = new LivePagedListBuilder<Integer, String>(factory,
  4. new PagedList.Config.Builder().setPageSize(20).setInitialLoadSizeHint(20)
  5. .setPrefetchDistance(3).build()).setInitialLoadKey(4).build();
  6. build.observe(this, it -> instance.submitList(it));

也就是将数据设置到adapter中,看到这,我们该想paging的源码该从哪里入手呢?想想就应该知道应该是从LivePagedListBuilder入手,好的,那就从LivePagedListBuilder的build()方法中去看看:

LivePagedListBuilder的build()方法:

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

就是简单调用了它的create()方法,那就看下它的create()方法:

  1. private static <Key, Value> LiveData<PagedList<Value>> create(
  2. @Nullable final Key initialLoadKey,//ItemKeyedDataSource会用到
  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. }

构建了一个ComputableLiveData对象,并把拉取数据的线程池传递进去,同时调用它的getLiveData()方法,接下来就去看下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. }

这里了创建了一个LiveData对象,并在它的onActive()方法中在传进来的线程池执行了mRefreshRunnable任务,这里就来看下这个任务里面执行了什么:

  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. //调用了compute()方法,这个方法在构建它对象的时候有实现
  16. value = compute();
  17. }
  18. if (computed) {
  19. //这里将获取到的值传递出去,
  20. mLiveData.postValue(value);
  21. }
  22. } finally {
  23. // release compute lock
  24. mComputing.set(false);
  25. }
  26. }
  27. // check invalid after releasing compute lock to avoid the following scenario.
  28. // Thread A runs compute()
  29. // Thread A checks invalid, it is false
  30. // Main thread sets invalid to true
  31. // Thread B runs, fails to acquire compute lock and skips
  32. // Thread A releases compute lock
  33. // We've left invalid in set state. The check below recovers.
  34. } while (computed && mInvalid.get());
  35. }
  36. };

这里再来细细看下它的compute()方法:

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

这里的initializeKey一是在ItemKeyedDataSource中会用到,在使用DataSource.Factory是,里面的create()方法就是这个时候调用的,可以看出这个方法只调用了一次,接下来就是创建一个PageList对象,这个对象就会通过LiveDate设置到adapter中去,到现在为止,还没给adapter设置数据,别急,接着往下看,PageList是通过Builder创建,最终调用的是PageList的create()方法,先来看看看:

  1. private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
  2. @NonNull Executor notifyExecutor,
  3. @NonNull Executor fetchExecutor,
  4. @Nullable BoundaryCallback<T> boundaryCallback,
  5. @NonNull Config config,
  6. @Nullable K key) {
  7. if (dataSource.isContiguous() || !config.enablePlaceholders) {
  8. int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
  9. if (!dataSource.isContiguous()) {
  10. //noinspection unchecked
  11. dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
  12. .wrapAsContiguousWithoutPlaceholders();
  13. if (key != null) {
  14. lastLoad = (int) key;
  15. }
  16. }
  17. ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
  18. return new ContiguousPagedList<>(contigDataSource,
  19. notifyExecutor,
  20. fetchExecutor,
  21. boundaryCallback,
  22. config,
  23. key,
  24. lastLoad);
  25. } else {
  26. return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
  27. notifyExecutor,
  28. fetchExecutor,
  29. boundaryCallback,
  30. config,
  31. (key != null) ? (Integer) key : 0);
  32. }
  33. }

通常创建的是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. }

这里的mDataSource就是通过DataSource.Factory的create()创建的,这里会调用到它的dispatchLoadInitial()方法,DataSource有三个子类,这里就只看PageKeyedDataSource,其他的两个类似:

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

可以看到一开始传递进来的参数这里封装到LoadInitialParams对象中去了,对于PageKeyedDataSource的loadInitial()方法主要是用于一开初始化的数据,一开始设置的参数传递给最终去请求数据。数据请求成功后会调用到callback对象的result()方法,最终反馈到adapter中刷新界面,result()方法里主要做的将线程切换到主线程中来,最后调用的是PageResult.Receiver的onPageResult方法,PageResult.Receiver是一个抽象类,在ContiguousPagedList实现并传递过去的,这里就来看下这里面做了什么:

  1. private PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
  2. // Creation thread for initial synchronous load, otherwise main thread
  3. // Safe to access main thread only state - no other thread has reference during construction
  4. @AnyThread
  5. @Override
  6. public void onPageResult(@PageResult.ResultType int resultType,
  7. @NonNull PageResult<V> pageResult) {
  8. if (pageResult.isInvalid()) {
  9. detach();
  10. return;
  11. }
  12. if (isDetached()) {
  13. // No op, have detached
  14. return;
  15. }
  16. //存储的是加载请求后的数据
  17. List<V> page = pageResult.page;
  18. //初始化数据时会回调到这里
  19. if (resultType == PageResult.INIT) {
  20. mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
  21. pageResult.positionOffset, ContiguousPagedList.this);
  22. if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
  23. // Because the ContiguousPagedList wasn't initialized with a last load position,
  24. // initialize it to the middle of the initial load
  25. mLastLoad =
  26. pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
  27. }
  28. } else if (resultType == PageResult.APPEND) {
  29. //加载初始化之前的数据会执行
  30. mStorage.appendPage(page, ContiguousPagedList.this);
  31. } else if (resultType == PageResult.PREPEND) {
  32. //加载初始化之后的数据会执行
  33. mStorage.prependPage(page, ContiguousPagedList.this);
  34. } else {
  35. throw new IllegalArgumentException("unexpected resultType " + resultType);
  36. }
  37. if (mBoundaryCallback != null) {
  38. boolean deferEmpty = mStorage.size() == 0;
  39. boolean deferBegin = !deferEmpty
  40. && resultType == PageResult.PREPEND
  41. && pageResult.page.size() == 0;
  42. boolean deferEnd = !deferEmpty
  43. && resultType == PageResult.APPEND
  44. && pageResult.page.size() == 0;
  45. deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
  46. }
  47. }
  48. };

返回的数据在这里取出来了,并传递到mStorage中去了,这是一个PagedStorage对象,那就在跟到PagedStorage去看看它的init()方法,其他的两个方法类似,感兴趣的可以自己去看下:

  1. void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
  2. @NonNull Callback callback) {
  3. init(leadingNulls, page, trailingNulls, positionOffset);
  4. callback.onInitialized(size());
  5. }
  6. private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) {
  7. mLeadingNullCount = leadingNulls;
  8. mPages.clear();
  9. mPages.add(page);
  10. mTrailingNullCount = trailingNulls;
  11. mPositionOffset = positionOffset;
  12. mStorageCount = page.size();
  13. // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
  14. // even if it will break if nulls convert.
  15. mPageSize = page.size();
  16. mNumberPrepended = 0;
  17. mNumberAppended = 0;
  18. }

这里存储数据的是mPages是一个ArrayList<List<T>>对象,其他的变量存储的是一些相关的数据,接着调用到了callback的onInitialized()方法,这个方法在ContiguousPagedList有实现:

  1. public void onInitialized(int count) {
  2. notifyInserted(0, count);
  3. }

很简单就是调用了他自己的notifyInserted()方法:

  1. void notifyInserted(int position, int count) {
  2. if (count != 0) {
  3. for (int i = mCallbacks.size() - 1; i >= 0; i--) {
  4. Callback callback = mCallbacks.get(i).get();
  5. if (callback != null) {
  6. callback.onInserted(position, count);
  7. }
  8. }
  9. }
  10. }

仔细一瞧,又是一个回调,那这个回调是哪里添加进来的呢?还记得PagedListAdapter的submitList么,对了,就是在这个方法里面添加的回调,PagedListAdapter里面使用的是代理模式,实际的功能是AsyncPagedListDiffer来处理的,所以这里就来看看AsyncPagedListDiffer的submitList()方法:

  1. public void submitList(final PagedList<T> pagedList) {
  2. if (pagedList != null) {
  3. if (mPagedList == null && mSnapshot == null) {
  4. mIsContiguous = pagedList.isContiguous();
  5. } else {
  6. if (pagedList.isContiguous() != mIsContiguous) {
  7. throw new IllegalArgumentException("AsyncPagedListDiffer cannot handle both"
  8. + " contiguous and non-contiguous lists.");
  9. }
  10. }
  11. }
  12. //pagedList如果是同一个是不会往下执行的,所以下拉刷新数据是必须要替换掉pagedList
  13. if (pagedList == mPagedList) {
  14. // nothing to do
  15. return;
  16. }
  17. // incrementing generation means any currently-running diffs are discarded when they finish
  18. final int runGeneration = ++mMaxScheduledGeneration;
  19. //传进来的pageList为null,会将之前传进来的pageList置为null,如果不置为null那么新传进来的pageList就会与之前的pageList的数据进行对比,将有变化的item数据进行更新
  20. if (pagedList == null) {
  21. int removedCount = getItemCount();
  22. if (mPagedList != null) {
  23. //清除数据传进来的回调
  24. mPagedList.removeWeakCallback(mPagedListCallback);
  25. mPagedList = null;
  26. } else if (mSnapshot != null) {
  27. mSnapshot = null;
  28. }
  29. // dispatch update callback after updating mPagedList/mSnapshot
  30. mUpdateCallback.onRemoved(0, removedCount);
  31. if (mListener != null) {
  32. mListener.onCurrentListChanged(null);
  33. }
  34. return;
  35. }
  36. //首次添加进来的时候会执行到这里,
  37. if (mPagedList == null && mSnapshot == null) {
  38. // fast simple first insert
  39. mPagedList = pagedList;
  40. //这里就是重点了,请求到的数据和界面刷新就是通过添加的这个回调来关联起来的
  41. pagedList.addWeakCallback(null, mPagedListCallback);
  42. // dispatch update callback after updating mPagedList/mSnapshot
  43. mUpdateCallback.onInserted(0, pagedList.size());
  44. if (mListener != null) {
  45. mListener.onCurrentListChanged(pagedList);
  46. }
  47. return;
  48. }
  49. //传进来pageList时,已经有一个已经存在的pageList了,这时就会将之前的pageList拷贝一份出来,同时将回调清空掉
  50. if (mPagedList != null) {
  51. // first update scheduled on this list, so capture mPages as a snapshot, removing
  52. // callbacks so we don't have resolve updates against a moving target
  53. mPagedList.removeWeakCallback(mPagedListCallback);
  54. mSnapshot = (PagedList<T>) mPagedList.snapshot();
  55. mPagedList = null;
  56. }
  57. if (mSnapshot == null || mPagedList != null) {
  58. throw new IllegalStateException("must be in snapshot state to diff");
  59. }
  60. final PagedList<T> oldSnapshot = mSnapshot;
  61. final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
  62. //有两个pageList时,会执行到这里,将对比的任务放到子线程去执行
  63. mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
  64. @Override
  65. public void run() {
  66. final DiffUtil.DiffResult result;
  67. result = PagedStorageDiffHelper.computeDiff(
  68. oldSnapshot.mStorage,
  69. newSnapshot.mStorage,
  70. mConfig.getDiffCallback());
  71. mMainThreadExecutor.execute(new Runnable() {
  72. @Override
  73. public void run() {
  74. if (mMaxScheduledGeneration == runGeneration) {
  75. latchPagedList(pagedList, newSnapshot, result);
  76. }
  77. }
  78. });
  79. }
  80. });
  81. }

上面有个方法需要注意,pagedList.addWeakCallback(),在这里添加了一格回调,这个回调就是前面说到的,当数据请求完成时,就会调用到这个回调,现在就来看下在这个回调里面具体做了什么:

  1. private PagedList.Callback mPagedListCallback = new PagedList.Callback() {
  2. @Override
  3. public void onInserted(int position, int count) {
  4. mUpdateCallback.onInserted(position, count);
  5. }
  6. @Override
  7. public void onRemoved(int position, int count) {
  8. mUpdateCallback.onRemoved(position, count);
  9. }
  10. @Override
  11. public void onChanged(int position, int count) {
  12. // NOTE: pass a null payload to convey null -> item
  13. mUpdateCallback.onChanged(position, count, null);
  14. }
  15. };

使用的也是代理,具体的操作交给了AdapterListUpdateCallback去执行,来看看这里面做了些什么东西:

  1. public final class AdapterListUpdateCallback implements ListUpdateCallback {
  2. @NonNull
  3. private final Adapter mAdapter;
  4. public AdapterListUpdateCallback(@NonNull Adapter adapter) {
  5. this.mAdapter = adapter;
  6. }
  7. public void onInserted(int position, int count) {
  8. this.mAdapter.notifyItemRangeInserted(position, count);
  9. }
  10. public void onRemoved(int position, int count) {
  11. this.mAdapter.notifyItemRangeRemoved(position, count);
  12. }
  13. public void onMoved(int fromPosition, int toPosition) {
  14. this.mAdapter.notifyItemMoved(fromPosition, toPosition);
  15. }
  16. public void onChanged(int position, int count, Object payload) {
  17. this.mAdapter.notifyItemRangeChanged(position, count, payload);
  18. }
  19. }

这一看就能明白,调用的是RecyclerView.Adapter的方法,也就是去刷新界面了,到这初始加载数据的流程就已经走完了一遍,接下来还有一个问题,那就是paging是如何是实现自动加载数据的,要想明白这个问题,那就得先来看看adapter的getItem()方法了,这个方法是获取对应item的数据,先来看看:

  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()方法,在这个方法里面就会去判断当前需不需要去加载数据,在PagedList.Config.Builder里面有个setPrefetchDistance()方法,这个方法就是设置距离边界还有多少个item就会开始去加载数据,这里就不在跟着进去了,好了,到这就结束了。

 

 

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

闽ICP备14008679号