上篇文章介绍了paging
+room
的使用,这篇主要介绍paging
+网络数据的使用和原理。
Jetpack笔记代码
本文源码基于SDK 29
网络数据来源于玩Android开放API,运行效果:
引入依赖:
def paging_version = "2.1.1" implementation "androidx.paging:paging-runtime:$paging_version" 复制代码
创建一个ViewModel
,
//PagingNetworkViewModel2.java LiveData<PagedList<ArticleBean.DataBean.Article>> mPageData; DataSource mDataSource; //数据源 //数据源工厂 private DataSource.Factory mFactory = new DataSource.Factory() { @Override public DataSource create() { if (mDataSource == null || mDataSource.isInvalid()) { //下拉刷新调用mDataSource.invalidate(),这时需要创建一个新的数据源 mDataSource = createDataSource(); } return mDataSource; } }; //创建数据源 private DataSource createDataSource() { return new ItemKeyedDataSource<Integer, ArticleBean.DataBean.Article>() { //paging首次加载数据 @Override public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<ArticleBean.DataBean.Article> callback) { //这3个load方法在子线程中执行,同步获取网络数据即可 callback.onResult(Api.getArticle(String.valueOf(mPage++))); } //paging加载更多数据,在滑动到配置好的位置时,自动触发 @Override public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<ArticleBean.DataBean.Article> callback) { callback.onResult(Api.getArticle(String.valueOf(mPage++))); } @Override public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<ArticleBean.DataBean.Article> callback) { //向前加载,忽略即可 } @Override public Integer getKey(@NonNull ArticleBean.DataBean.Article item) { return item.getId(); } }; } public LiveData<PagedList<ArticleBean.DataBean.Article>> getPageData() { if (null == mPageData) { PagedList.Config config = new PagedList.Config.Builder() .setPageSize(20) //分页大小 .setInitialLoadSizeHint(20) //首次加载大小 .setPrefetchDistance(10) //预加载距离:还剩10个就要滑到底了,就进行预加载 .build(); mPageData = new LivePagedListBuilder(mFactory, config).build(); } return mPageData; } //下拉刷新 public void refresh() { mPage = 0; mDataSource.invalidate(); } 复制代码
创建适配器NetworkListAdapter2
继承自PagedListAdapter
,这里只需提供一个数据diff的规则DiffUtil.ItemCallback
即可,onCreateViewHolder
和onBindViewHolder
的用法和老的RecyclerView.Adapter
没区别已省略,
//NetworkListAdapter2.java NetworkListAdapter2() { super(new DiffUtil.ItemCallback<ArticleBean.DataBean.Article>() { @Override public boolean areItemsTheSame(@NonNull ArticleBean.DataBean.Article oldItem, @NonNull ArticleBean.DataBean.Article newItem) { return oldItem.getId() == newItem.getId(); } @Override public boolean areContentsTheSame(@NonNull ArticleBean.DataBean.Article oldItem, @NonNull ArticleBean.DataBean.Article newItem) { return oldItem.equals(newItem); } }); } 复制代码
在activity中使用,
//PagingNetworkActivity2.java onCreate(Bundle savedInstanceState) { //关闭加载更多,使用paging的预加载即可 mBinding.refreshArticle.setEnableLoadMore(false); mBinding.refreshArticle.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(@NonNull RefreshLayout refreshLayout) { //下拉刷新 mViewModel.refresh(); } }); mAdapter = new NetworkListAdapter2(); mBinding.rvArticle.setAdapter(mAdapter); mBinding.rvArticle.setLayoutManager(new LinearLayoutManager(this)); mViewModel.getPageData().observe(this, new Observer<PagedList<ArticleBean.DataBean.Article>>() { @Override public void onChanged(PagedList<ArticleBean.DataBean.Article> articles) { mAdapter.submitList(articles); } }); } 复制代码
这几个类名都加了后缀2,这是因为笔者先写了一套老的RecyclerView.Adapter
使用方案,用来对比两套实现方案,代码见Jetpack笔记代码,欢迎star。
同样,还是选择了几个问题进行分析,因为带着问题去跟进才能更聚焦:
首先来到PagingNetworkViewModel2
里创建数据源的代码,在loadAfter
方法里打个断点,上滑加载更多,查看调用栈,
因为切了线程,调用栈不是很全,点下第三行来到这里,
//ContiguousPagedList.java @MainThread private void scheduleAppend() { mLoadStateManager.setState(LoadType.END, LoadState.LOADING, null); mBackgroundThreadExecutor.execute(new Runnable() { @Override public void run() { mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize, mMainThreadExecutor, mReceiver); } }); } 复制代码
给这个方法的第一行打一个断点,再触发一次加载更多,看看哪里调了scheduleAppend
,
这时调用链就很清晰了,在onBindViewHolder
中我们调了getItem
取出条目数据,进而触发预加载的逻辑。下面顺着这个链路跟进看看,
//PagedListAdapter.java protected T getItem(int position) { return mDiffer.getItem(position); } //AsyncPagedListDiffer.java public T getItem(int index) { mPagedList.loadAround(index); } //PagedList.java public void loadAround(int index) { loadAroundInternal(index); } //ContiguousPagedList.java protected void loadAroundInternal(int index) { scheduleAppend(); } private void scheduleAppend() { //这里切到了子线程 mBackgroundThreadExecutor.execute(new Runnable() { @Override public void run() { mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize, mMainThreadExecutor, mReceiver); } }); } //ItemKeyedDataSource.java final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize, @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver) { //子线程回调 loadAfter(new LoadParams<>(getKey(currentEndItem), pageSize), new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver)); } 复制代码
然后在子线程回调的方法loadAfter
里,我们同步获取网络数据,
//PagingNetworkViewModel2.java private DataSource createDataSource() { return new ItemKeyedDataSource<Integer, ArticleBean.DataBean.Article>() { @Override public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<ArticleBean.DataBean.Article> callback) { //同步获取网络数据,然后callback.onResult callback.onResult(Api.getArticle(String.valueOf(mPage++))); } } } 复制代码
callback.onResult
继续分发,链条有点长,就直接列出来了,
ItemKeyedDataSource.LoadCallbackImpl#onResult DataSource.LoadCallbackHelper#dispatchToReceiver(这里切回了主线程) DataSource.LoadCallbackHelper#dispatchOnCurrentThread PageResult.Receiver#onPageResult PagedStorage#appendPage ContiguousPagedList#onPageAppended PagedList#notifyInserted PagedList.Callback#onInserted(在AsyncPagedListDiffer类里) AdapterListUpdateCallback#onInserted(这里调了mAdapter.notifyItemRangeInserted添加新数据)
首先我们会调用mDataSource.invalidate()
,然后来到,
//DataSource.java public void invalidate() { if (mInvalid.compareAndSet(false, true)) { for (InvalidatedCallback callback : mOnInvalidatedCallbacks) { callback.onInvalidated(); } } } //LivePagedListBuilder.java final DataSource.InvalidatedCallback mCallback = new DataSource.InvalidatedCallback() { @Override public void onInvalidated() { invalidate(); } }; //ComputableLiveData.java public void invalidate() { ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable); //mInvalidationRunnable - mRefreshRunnable - compute() } //LivePagedListBuilder.java protected PagedList<Value> compute() { //调用我们提供的数据源工厂类实现,创建数据源 mDataSource = dataSourceFactory.create(); mList = new PagedList.Builder<>(mDataSource, config) .setNotifyExecutor(notifyExecutor) .setFetchExecutor(fetchExecutor) .setBoundaryCallback(boundaryCallback) .setInitialKey(initializeKey) .build(); return mList; } //ComputableLiveData.java //执行完compute,设置值,通知观察者 mLiveData.postValue(value); //回到了activity mViewModel.getPageData().observe(this, new Observer<PagedList<ArticleBean.DataBean.Article>>() { @Override public void onChanged(PagedList<ArticleBean.DataBean.Article> articles) { //重新提交数据 mAdapter.submitList(articles); } }); 复制代码
回到我们的数据源工厂类实现,
//PagingNetworkViewModel2.java private DataSource.Factory mFactory = new DataSource.Factory() { @Override public DataSource create() { if (mDataSource == null || mDataSource.isInvalid()) { //下拉刷新调用mDataSource.invalidate(),这时需要创建一个新的数据源 mDataSource = createDataSource(); } return mDataSource; } }; 复制代码
mDataSource.invalidate()
下拉刷新必须创建新的数据源,否则将引起死循环,
//LivePagedListBuilder.java protected PagedList<Value> compute() { do { mDataSource = dataSourceFactory.create(); mDataSource.addInvalidatedCallback(mCallback); mList = new PagedList.Builder<>(mDataSource, config) .setNotifyExecutor(notifyExecutor) .setFetchExecutor(fetchExecutor) .setBoundaryCallback(boundaryCallback) .setInitialKey(initializeKey) .build(); } while (mList.isDetached());//始终为true,引起死循环 return mList; } 复制代码
Room
无缝结合本文使用 mdnice 排版