上篇文章介绍了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即可,onCreateViewHolderonBindViewHolder的用法和老的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示例

更多相关文章

  1. “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
  2. Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
  3. 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
  4. android入门篇之ContentProvider学习笔记
  5. org.json.JSONException: Value  of type java.lang.String cann
  6. Android(安卓)FileInputStream类的使用
  7. android (一)RecycleView组件的使用
  8. Android开发点滴(13) -- Android数据库随同Android应用一同发布
  9. Android: ListView排序及过滤

随机推荐

  1. Android(安卓)App整体架构设计的思考(一)
  2. Property Animation - 概述和工作原理
  3. Android(安卓)中动画的实现
  4. 时间最会见缝插针——大三下总结
  5. 记一次惨痛经历(安装Android(安卓)Studio3
  6. Android(安卓)Studio下使用AIDL创建和使
  7. 《Android群英传》读书笔记(6)第六章:Androi
  8. android调起QQ聊天,QQ个人资料,QQ群资料
  9. 主页信号条显示的信号强度并不准确,教你看
  10. Android各种好看吐司设计