上篇文章介绍了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
无缝结合