美文网首页
jectpack系列——paging源码分析

jectpack系列——paging源码分析

作者: Peakmain | 来源:发表于2021-11-05 18:10 被阅读0次

相关文章系列

使用

  • paging实际就是分页加载,它把几种常见的分页机制提供了统一的解决方案
  • paging支持的架构类型
    • 网络数据
      分页机制所设计的API接口不一样,但是总体可以分为3种,因此paging提供了3种不同的解决方案,分别是:PositionDataSource、PageKeyedDataSource、ItemKeyedDataSource
    • 数据库
      替换数据源
  • 工作原理


    image.png
  • 3个核心类
    • pagedListAdapter
      • 如果需要使用paging组件,适配器需要继承pagedListAdapter
    • pagedList
      • pagedList负责通知DataSource何时和如何获取数据,从DataSource获取的数据将存储在pagedList中
    • DataSource
      • 执行具体的数据载入工作,数据可以来自网络,也可以来自本地数据、数据库数据,根据分页机制不同,paging提供了3种DataSource
      • 数据的载入需要再工作线程中进行

PositionDataSource

  • 适用于可通过任意位置加载数据,且目标数据源数量固定的情况。
  • 比如:从数据库的第100条数据开始加载20条数据
  • DataSource创建
public class StudentDataSource extends PositionalDataSource<Student> {

    /**
     * 加载第一页数据的时候执行
     *
     * @param params
     * @param callback
     */
    @Override
    public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
        /**
         * position:位置
         * totalCount:总的大小
         */
        callback.onResult(getStudents(0, 20), 0,1000);
    }

    /**
     * 有了初始化数据之后,滑动的时候如果需要加载数据的话,会调用此方法
     *
     * @param params
     * @param callback
     */
    @Override
    public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Student> callback) {
        callback.onResult(getStudents(params.startPosition, params.loadSize));
    }

    /**
     * 假的数据源
     */
    private List<Student> getStudents(int startPosition, int pageSize) {
        List<Student> students = new ArrayList<>();
        for (int i = startPosition; i < startPosition + pageSize; i++) {
            Student student = new Student("Id是:" + i, "名字:" + i);
            students.add(student);
        }
        return students;
    }
}
  • 数据工厂
/**
 * author :Peakmain
 * createTime:2021/11/5
 * mail:2726449200@qq.com
 * describe:PositionalDataSource对应的Key是Integer
 */
public class StudentDataSourceFactory extends DataSource.Factory<Integer,Student> {
    @NonNull
    @Override
    public DataSource<Integer, Student> create() {
        return new StudentDataSource();
    }
}
  • pageList的创建
public class StudentViewModel extends ViewModel {
    private final LiveData<PagedList<Student>> listLiveData;

    public StudentViewModel() {
        StudentDataSourceFactory factory = new StudentDataSourceFactory();
        this.listLiveData = new LivePagedListBuilder<>(factory, 20).build();
    }

    public LiveData<PagedList<Student>> getListLiveData() {
        return listLiveData;
    }
}
  • PagedListAdapter的创建
public class StudentAdapter extends PagedListAdapter<Student, StudentAdapter.CommonRecyclerViewHolder> {
    private static DiffUtil.ItemCallback<Student> DIFF_STUDENT = new DiffUtil.ItemCallback<Student>() {
        //一般比较的是唯一内容:id
        @Override
        public boolean areItemsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
            return oldItem.getId().equals(newItem.getId());
        }

        //对象本身的比较
        @Override
        public boolean areContentsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
            return oldItem.equals(newItem);
        }
    };

    public StudentAdapter() {
        //diffCallback比较的行为
        super(DIFF_STUDENT);
    }

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

    @Override
    public void onBindViewHolder(@NonNull CommonRecyclerViewHolder holder, int position) {
        Student student = getItem(position);
        holder.mTvName.setText(student.getName());
        holder.mTvId.setText(student.getId());
    }

    public static class CommonRecyclerViewHolder extends RecyclerView.ViewHolder {
        private TextView mTvId, mTvName;

        public CommonRecyclerViewHolder(@NonNull View itemView) {
            super(itemView);
            mTvId = itemView.findViewById(R.id.tv_id);
            mTvName = itemView.findViewById(R.id.tv_name);
        }
    }
}

使用

  val studentAdapter = StudentAdapter()
        recyclerView.adapter = studentAdapter
        recyclerView.layoutManager=LinearLayoutManager(context)
        val viewModel=ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()).get(StudentViewModel::class.java)
        viewModel.listLiveData.observe(viewLifecycleOwner, Observer {
            studentAdapter.submitList(it)
        })

ItemKeyedDataSource<T>

  • 适用于目标数据的加载依赖特定的item的信息,即key字段包含的是item中的信息
  • 如:根据第N项的信息加载第n+1项的数据,传参中需要传入第N项的ID时
  • 场景:论坛应用评论信息的请求
public class StudentDataSource1 extends ItemKeyedDataSource<Integer,Student> {


    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Student> callback) {

    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Student> callback) {

    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Student> callback) {

    }

    @NonNull
    @Override
    public Integer getKey(@NonNull Student item) {
        return null;
    }
}

PageKeyedDataSource

  • 如果页面需要实现上一页、下一页,需要将请求的Token传递到下一步时使用
public class StudentDataSource2 extends PageKeyedDataSource<Integer,Student> {


    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Student> callback) {

    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Student> callback) {

    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Student> callback) {

    }
}

源码分析

paging的数据是怎么初始化的?

我们入口首先来选择pagedList,因为这里是数据的集合点
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;
           //代码省略
            @Override
            protected PagedList<Value> compute() {
                 //代码省略
                } while (mList.isDetached());
                return mList;
            }
        }.getLiveData();

上面代码比较长,我们一点点分析

  • 1、ComputableLiveData构造函数源码分析
    public ComputableLiveData(@NonNull Executor executor) {
        mExecutor = executor;
        mLiveData = new LiveData<T>() {
            @Override
            protected void onActive() {
                mExecutor.execute(mRefreshRunnable);
            }
        };
    }
    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 {
                        mComputing.set(false);
                    }
                }
            } while (computed && mInvalid.get());
        }
    };
    protected abstract T compute();

最终会执行到create的ComputableLiveData的compute

  • 2、compute源码分析
 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;
            @Override
            protected PagedList<Value> compute() {
                @Nullable Key initializeKey = initialLoadKey;
                do {
                     //dataSourceFactory是我们自己传入的StudentDataSourceFactory,此时会调用StudentDataSourceFactory的.create方法
                    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();
    }
        public PagedList<Value> build() {
            return PagedList.create(
                    mDataSource,
                    mNotifyExecutor,
                    mFetchExecutor,
                    mBoundaryCallback,
                    mConfig,
                    mInitialKey);
        }
    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);
        }
    }
  • 调用传入的Factorty的create()创建DataSource实例
  • 创建并返回pagedList实例
  • create主要根据条件dataSource.isContiguous() || !config.enablePlaceholders分别创建ContiguousPagedList和TiledPagedList
  • isContiguous其实只是区分三个自定义DataSource类型而已,PositionalDataSource创建TiledPagedList,其他都是创建ContiguousPagedList
  • 3、ContiguousPagedList构造源码分析
    TiledPagedList(@NonNull PositionalDataSource<T> dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, @Nullable BoundaryCallback<T> boundaryCallback, @NonNull Config config, int position) {
        super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor, boundaryCallback, config);
        this.mDataSource = dataSource;
        int pageSize = this.mConfig.pageSize;
        this.mLastLoad = position;
        if (this.mDataSource.isInvalid()) {
            this.detach();
        } else {
            int firstLoadSize = Math.max(this.mConfig.initialLoadSizeHint / pageSize, 2) * pageSize;
            int idealStart = position - firstLoadSize / 2;
            int roundedPageStart = Math.max(0, idealStart / pageSize * pageSize);
              //这里mDataSource实际是PositionDataSource
            this.mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize, pageSize, this.mMainThreadExecutor, this.mReceiver);
        }

    }
   void dispatchLoadInitial(@Nullable Integer position, int initialLoadSize, int pageSize,
                boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
                @NonNull PageResult.Receiver<Value> receiver) {
            //代码省略
            mSource.dispatchLoadInitial(false, position, initialLoadSize,
                    pageSize, mainThreadExecutor, receiver);
        }
    final void dispatchLoadInitial(boolean acceptCount,
            int requestedStartPosition, int requestedLoadSize, int pageSize,
            @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
        LoadInitialCallbackImpl<T> callback =
                new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);
        loadInitial(params, callback);
    }
    //最终实际调用的是自己创建的StudentDataSource的loadInitial方法
    public abstract void loadInitial(
            @NonNull LoadInitialParams params,
            @NonNull LoadInitialCallback<T> callback);
  • 创建PagedStorage实例,主要根据滑动的位置显示是否需要加载数据
  • dispatchLoadInitial方法调用抽象函数loadInitial,前面我们知道loadInitial主要用于初始化数据的加载

paging数据是怎么显示的呢

我们回到自己实现loadInitial的方法

    @Override
    public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
        callback.onResult(getStudents(0, 20), 0,1000);
    }

细心的人肯定发现了callback就是LoadInitialCallbackImpl

        public void onResult(@NonNull List<T> data, int position, int totalCount) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);

                if (mCountingEnabled) {
                       //这里实际是false
                    int trailingUnloadedCount = totalCount - position - data.size();
                    mCallbackHelper.dispatchResultToReceiver(
                            new PageResult<>(data, position, trailingUnloadedCount, 0));
                } else {
                    //所以走到这里
                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
                }
            }
        }
        void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
            Executor executor;
            if (executor != null) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        mReceiver.onPageResult(mResultType, result);
                    }
                });
            } else {
                mReceiver.onPageResult(mResultType, result);
            }
        }
    Receiver<T> mReceiver = new Receiver<T>() {
        @AnyThread
        public void onPageResult(int type, @NonNull PageResult<T> pageResult) {
            if (pageResult.isInvalid()) {
                TiledPagedList.this.detach();
            } else if (!TiledPagedList.this.isDetached()) {
                if (type != 0 && type != 3) {
                    throw new IllegalArgumentException("unexpected resultType" + type);
                } else {
                    List<T> page = pageResult.page;
                    if (TiledPagedList.this.mStorage.getPageCount() == 0) {
                        TiledPagedList.this.mStorage.initAndSplit(pageResult.leadingNulls, page, pageResult.trailingNulls, pageResult.positionOffset, TiledPagedList.this.mConfig.pageSize, TiledPagedList.this);
                    }
                }
            }
        }
    };

  • PageResult的三个数据类型分别对应ItemKeyDataSource的三个方法
    loadInitial:对应初始化状态PageResult.INIT
    loadBefore:对应初始化状态PagerResult.PREPEND
    loadAfter:对应初始化PageResultAPPEND
  • 2、init源码分析
     void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList,
            int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) {
        callback.onInitialized(size());
    }
    public void onInitialized(int count) {
        notifyInserted(0, count);
    }

    void notifyInserted(int position, int count) {
        if (count != 0) {
            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
                final Callback callback = mCallbacks.get(i).get();
                if (callback != null) {
                    callback.onInserted(position, count);
                }
            }
        }
    }
  • 加载的数据保存在PagedStorage中,并记录加载的位置信息
  • 加载完成后根据数据的变化,回调callback.onInserted()通知数据改变的数量和位置
public class AsyncPagedListDiffer<T> {
    public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
            @NonNull DiffUtil.ItemCallback<T> diffCallback) {
        mUpdateCallback = new AdapterListUpdateCallback(adapter);
        mConfig = new AsyncDifferConfig.Builder<>(diffCallback).build();
    }
    public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback, @NonNull AsyncDifferConfig<T> config) {
        class NamelessClass_1 extends Callback {
            NamelessClass_1() {
            }

            public void onInserted(int position, int count) {
                AsyncPagedListDiffer.this.mUpdateCallback.onInserted(position, count);
            }

            public void onRemoved(int position, int count) {
                AsyncPagedListDiffer.this.mUpdateCallback.onRemoved(position, count);
            }

            public void onChanged(int position, int count) {
                AsyncPagedListDiffer.this.mUpdateCallback.onChanged(position, count, (Object)null);
            }
        }

        this.mPagedListCallback = new NamelessClass_1();
        this.mUpdateCallback = listUpdateCallback;
        this.mConfig = config;
    }
}
 public void onInserted(int position, int count) {
        mAdapter.notifyItemRangeInserted(position, count);
  }

最终实际调用的RecycleView的adapter的notifyItemRangeInserted方法,至此paging源码分析已全部分析完了

最后贴上时序图
Paging源码分析时序图.jpg

相关文章

网友评论

      本文标题:jectpack系列——paging源码分析

      本文链接:https://www.haomeiwen.com/subject/tdurzltx.html