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即可,onCreateViewHolderonBindViewHolder的用法和老的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方法里打个断点,上滑加载更多,查看调用栈,

Android-Jetpack笔记-Paging结合网络数据_第1张图片

因为切了线程,调用栈不是很全,点下第三行来到这里,

//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

Android-Jetpack笔记-Paging结合网络数据_第2张图片

这时调用链就很清晰了,在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示例

Android-Jetpack笔记-Paging结合网络数据_第3张图片

你可能感兴趣的:(Android)