美文网首页
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