Android-Jetpack笔记-Paging结合网络数据
上篇文章介绍了paging
+room
的使用,这篇主要介绍paging
+网络数据的使用和原理。
Jetpack笔记代码
本文源码基于SDK 29
使用
网络数据来源于玩Android开放API,运行效果:
引入依赖:
def paging_version = "2.1.1"implementation "androidx.paging:paging-runtime:$paging_version"
创建一个ViewModel
,
//PagingNetworkViewModel2.javaLiveData<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.javaNetworkListAdapter2() { 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.javaonCreate(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。
原理
同样,还是选择了几个问题进行分析,因为带着问题去跟进才能更聚焦:
- 预加载怎么触发加载更多的
- mDataSource.invalidate()怎么实现下拉刷新的
预加载怎么触发加载更多的
首先来到PagingNetworkViewModel2
里创建数据源的代码,在loadAfter
方法里打个断点,上滑加载更多,查看调用栈,
因为切了线程,调用栈不是很全,点下第三行来到这里,
//ContiguousPagedList.java@MainThreadprivate 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.javaprotected T getItem(int position) { return mDiffer.getItem(position);}//AsyncPagedListDiffer.javapublic T getItem(int index) { mPagedList.loadAround(index);}//PagedList.javapublic void loadAround(int index) { loadAroundInternal(index);}//ContiguousPagedList.javaprotected 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.javafinal 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.javaprivate 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()怎么实现下拉刷新的
首先我们会调用mDataSource.invalidate()
,然后来到,
//DataSource.javapublic void invalidate() { if (mInvalid.compareAndSet(false, true)) { for (InvalidatedCallback callback : mOnInvalidatedCallbacks) { callback.onInvalidated(); } }}//LivePagedListBuilder.javafinal DataSource.InvalidatedCallback mCallback = new DataSource.InvalidatedCallback() { @Override public void onInvalidated() { invalidate(); }};//ComputableLiveData.javapublic void invalidate() { ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable); //mInvalidationRunnable - mRefreshRunnable - compute()}//LivePagedListBuilder.javaprotected 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);//回到了activitymViewModel.getPageData().observe(this, new Observer<PagedList<ArticleBean.DataBean.Article>>() { @Override public void onChanged(PagedList<ArticleBean.DataBean.Article> articles) { //重新提交数据 mAdapter.submitList(articles); }});
回到我们的数据源工厂类实现,
//PagingNetworkViewModel2.javaprivate DataSource.Factory mFactory = new DataSource.Factory() { @Override public DataSource create() { if (mDataSource == null || mDataSource.isInvalid()) { //下拉刷新调用mDataSource.invalidate(),这时需要创建一个新的数据源 mDataSource = createDataSource(); } return mDataSource; }};
mDataSource.invalidate()
下拉刷新必须创建新的数据源,否则将引起死循环,
//LivePagedListBuilder.javaprotected 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;}
优缺点
- 优点:
- 自带分页,预加载处理
- 子线程diff,主线程局部刷新
- 可以和
Room
无缝结合
- 缺点:
- 使用复杂,有待封装
参考文章
- 掘金-Android官方架构组件Paging:分页库的设计美学
- csdn-Android Paging数据刷新及原理解析
- GitHub-谷歌jetpack示例
更多相关文章
- “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
- Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
- 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
- android入门篇之ContentProvider学习笔记
- org.json.JSONException: Value of type java.lang.String cann
- Android(安卓)FileInputStream类的使用
- android (一)RecycleView组件的使用
- Android开发点滴(13) -- Android数据库随同Android应用一同发布
- Android: ListView排序及过滤