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.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。
原理
同样,还是选择了几个问题进行分析,因为带着问题去跟进才能更聚焦:
- 预加载怎么触发加载更多的
- mDataSource.invalidate()怎么实现下拉刷新的
预加载怎么触发加载更多的
首先来到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()怎么实现下拉刷新的
首先我们会调用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;
}
优缺点
- 优点:
- 自带分页,预加载处理
- 子线程diff,主线程局部刷新
- 可以和
Room
无缝结合
- 缺点:
- 使用复杂,有待封装
参考文章
- 掘金-Android官方架构组件Paging:分页库的设计美学
- csdn-Android Paging数据刷新及原理解析
- GitHub-谷歌jetpack示例
- [WCF安全系列]谈谈WCF的客户端认证[Windows认证]
- ls命令实现分析
- [WCF安全系列]谈谈WCF的客户端认证[X.509证书认证]
- Openstack Trove概要
- [WCF安全系列]实例演示:TLS/SSL在WCF中的应用[SSL over TCP]
- [WCF安全系列]谈谈WCF的客户端认证[用户名/密码认证]
- [WCF安全系列]绑定、安全模式与客户端凭证类型:BasicHttpBinding
- [WCF安全系列]服务凭证(Service Credential)与服务身份(Service Identity)
- 如何正确的对安卓手机进行数据恢复?
- [WCF安全系列]绑定、安全模式与客户端凭证类型:WSHttpBinding与WSDualHttpBinding
- Python中list的遍历
- Python中的参数传递与解析
- [WCF安全系列]实例演示:TLS/SSL在WCF中的应用[HTTPS]
- QEMU3 - 使用ceph来存储QEMU镜像
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 利用hexo和github或coding 搭免费个人博客
- window 指令之 tree
- Go语言入门(六)结构体后续&指针
- 一天一大 leet(二叉树的序列化与反序列化)难度:困难 DAY-16
- 一天一大 leet(三数之和)难度:中等 DAY-12
- MongoDB Docker版本:基础入门和复制集
- Django连接MySql使用models处理数据
- 一天一大 leet(爬楼梯)难度:简单 DAY-13
- 一天一大 leet(最长公共前缀)难度:简单 DAY-15
- Go语言入门(七)goroutine和channel
- 一天一大 leet(从先序遍历还原二叉树)难度:困难 DAY-18
- Go语言入门(八)线程安全&锁
- 一天一大 leet(最佳观光组合)难度:中等 DAY-17
- Django环境搭建
- javascript 中的位运算符