在平时使用RecyclerView
时,下拉刷新时先更新数据然后调用Adapter.notifyDataSetChanged
全量更新,修改条目时则先更新数据,然后调用Adapter.notifyItemXXX
进行局部更新。Paging
出现后,则只需要对数据进行变更,无需手动刷新UI,其内部会对数据源进行diff
操作(基于Myers 差分算法),然后选择合适的方式刷新UI,同时他还处理了数据的分页加载。本文主要结合Room
数据库进行使用和分析。
Jetpack笔记代码
本文源码基于SDK 29
引入依赖:
def paging_version = "2.1.1" implementation "androidx.paging:paging-runtime:$paging_version" 复制代码
创建一个ViewModel
//PagingViewModel.java private UserDao mUserDao; //dao对象用来从数据库中获取数据 private LiveData<PagedList<User>> mLiveData; //可观察的数据源 public LiveData<PagedList<User>> getLiveData() { if (null == mLiveData && null != mUserDao) { //room支持直接返回paging所需的数据源工厂类DataSource.Factory DataSource.Factory<Integer, User> factory = mUserDao.queryUsersLive(); //配置参数 PagedList.Config config = new PagedList.Config.Builder() .setPageSize(15) // 分页加载的数量 .setInitialLoadSizeHint(30) // 初次加载的数量 .setPrefetchDistance(10) // 预取数据的距离 .setEnablePlaceholders(false) // 是否启用占位符(本地数据比较合适,因为远程数据是未知的) .build(); //用room返回的DataSource.Factory来构建数据列表 mLiveData = new LivePagedListBuilder<>(factory, config).build(); } return mLiveData; } 复制代码
创建适配器MyListAdapter
继承自PagedListAdapter
,
//MyListAdapter.java MyListAdapter() { super(new DiffUtil.ItemCallback<User>() { @Override public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) { //是否是同一个item,一般用数据源的唯一标识 return oldItem.getId() == newItem.getId(); } @Override public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) { //内容是否发生变更,一般重写equals return oldItem.equals(newItem); } }); } //创建ViewHolder,跟RecyclerView用法一样 UserAdapterHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { RvItemPagingBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.rv_item_paging, parent, false); return new UserAdapterHolder(binding); } //ViewHolder绑定数据,跟RecyclerView用法一样 public void onBindViewHolder(@NonNull UserAdapterHolder holder, int position) { final User user = getItem(position); holder.getBinding().setUser(user); } class UserAdapterHolder extends RecyclerView.ViewHolder { private RvItemPagingBinding mBinding; UserAdapterHolder(RvItemPagingBinding binding) { super(binding.getRoot()); mBinding = binding; } public RvItemPagingBinding getBinding() { return mBinding; } } 复制代码
在activity中使用,
//PagingActivity.java onCreate(Bundle savedInstanceState) { mViewModel.setUserDao(mUserDao); mViewModel.getLiveData().observe(this, new Observer<PagedList<User>>() { @Override public void onChanged(PagedList<User> users) { //提交数据 mListAdapter.submitList(users); } }); mBinding.rvUser.setAdapter(mListAdapter); } 复制代码
运行即可。
下面将带着两个问题,逐步分析内部实现:
PagedListAdapter
是适配器,核心能力交给了AsyncPagedListDiffer
处理,AsyncPagedListDiffer
能对数据进行diff处理,
//AsyncPagedListDiffer.java //构造方法 AsyncPagedListDiffer(RecyclerView.Adapter adapter,DiffUtil.ItemCallback<T> diffCallback) { //把适配器包装到UpdateCallback mUpdateCallback = new AdapterListUpdateCallback(adapter); mConfig = new AsyncDifferConfig.Builder<>(diffCallback).build(); } 复制代码
来到AdapterListUpdateCallback
,
//AdapterListUpdateCallback.java AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) { mAdapter = adapter; } void onInserted(int position, int count) { //UpdateCallback在被触发时,将行为委托给适配器,这里就是熟悉的局部刷新代码了 mAdapter.notifyItemRangeInserted(position, count); } void onMoved(int fromPosition, int toPosition) { mAdapter.notifyItemMoved(fromPosition, toPosition); } 复制代码
那AdapterListUpdateCallback
又是什么时候被触发的呢,PagedListAdapter
调用submitList
时,委托给了AsyncPagedListDiffer
,
//AsyncPagedListDiffer.java submitList(final PagedList<T> pagedList,final Runnable commitCallback) { if (mPagedList == null && mSnapshot == null) { //初始化的时候,直接这里回调,不走后面的差异计算 mUpdateCallback.onInserted(0, pagedList.size()); return; } //去子线程计算数据差异 final DiffUtil.DiffResult result = PagedStorageDiffHelper.computeDiff( oldSnapshot.mStorage, //老数据快照 newSnapshot.mStorage, //新数据快照 mConfig.getDiffCallback()); //回到主线程,把差异结果进行分发 latchPagedList(pagedList, newSnapshot, result, oldSnapshot.mLastLoad, commitCallback); } latchPagedList(PagedList<T> newList,PagedList<T> diffSnapshot,DiffUtil.DiffResult diffResult, int lastAccessIndex,Runnable commitCallback) { //继续分发,这里传入了UpdateCallback PagedStorageDiffHelper.dispatchDiff(mUpdateCallback, previousSnapshot.mStorage, newList.mStorage, diffResult); } 复制代码
来到PagedStorageDiffHelper
,这里就可以看见AdapterListUpdateCallback
被回调了,具体的计算逻辑暂不深究,
//PagedStorageDiffHelper.java dispatchDiff(ListUpdateCallback callback,final PagedStorage<T> oldList, final PagedStorage<T> newList,final DiffUtil.DiffResult diffResult) { if (trailingOld == 0 && trailingNew == 0 && leadingOld == 0 && leadingNew == 0) { // Simple case, dispatch & return diffResult.dispatchUpdatesTo(callback); //回调 return; } // First, remove or insert trailing nulls if (trailingOld > trailingNew) { int count = trailingOld - trailingNew; callback.onRemoved(oldList.size() - count, count); //回调 } else if (trailingOld < trailingNew) { callback.onInserted(oldList.size(), trailingNew - trailingOld); //回调 } // Second, remove or insert leading nulls if (leadingOld > leadingNew) { callback.onRemoved(0, leadingOld - leadingNew); //回调 } else if (leadingOld < leadingNew) { callback.onInserted(0, leadingNew - leadingOld); //回调 } } 复制代码
总结一下,就是PagedListAdapter
调用submitList
,然后委托给AsyncPagedListDiffer
,其内部进行数据的差异计算,然后回调AdapterListUpdateCallback
使PagedListAdapter
调用notifyItemRangeXXX
进行局部刷新UI。
分析前需要先理解几个概念,PagedList
是具体的数据列表,由DataSource
数据源提供数据,DataSource
又由DataSource.Factory
工厂类创建。
首先来到LivePagedListBuilder.build()
,
//LivePagedListBuilder.java LiveData<PagedList<Value>> build() { return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory, ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor); } <Key, Value> LiveData<PagedList<Value>> create( @Nullable final Key initialLoadKey, @NonNull final PagedList.Config config, @Nullable final PagedList.BoundaryCallback boundaryCallback, @NonNull final DataSource.Factory<Key, Value> dataSourceFactory, @NonNull final Executor notifyExecutor, @NonNull final Executor fetchExecutor) { return new ComputableLiveData<PagedList<Value>>(fetchExecutor) { private PagedList<Value> mList; private DataSource<Key, Value> mDataSource; @Override protected PagedList<Value> compute() { //指定compute逻辑 @Nullable Key initializeKey = initialLoadKey; if (mList != null) { initializeKey = (Key) mList.getLastKey(); } do { //这里调了dataSourceFactory来创建dataSource mDataSource = dataSourceFactory.create(); mList = new PagedList.Builder<>(mDataSource, config) .setNotifyExecutor(notifyExecutor) .setFetchExecutor(fetchExecutor) .setBoundaryCallback(boundaryCallback) .setInitialKey(initializeKey) .build(); } while (mList.isDetached()); return mList; } }.getLiveData(); } 复制代码
先看mDataSource = dataSourceFactory.create()
这行,具体实现在生成类UserDao_Impl
,
//UserDao_Impl.java @Override public DataSource.Factory<Integer, User> queryUsersLive() { final String _sql = "SELECT * FROM t_user"; final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0); return new DataSource.Factory<Integer, User>() { @Override public LimitOffsetDataSource<User> create() { //DataSource有多种类型,这里返回了LimitOffsetDataSource return new LimitOffsetDataSource<User>(__db, _statement, false , "t_user") { @Override protected List<User> convertRows(Cursor cursor) { //查询数据库,得到List<User> final int _cursorIndexOfMId = CursorUtil.getColumnIndexOrThrow(cursor, "id"); final int _cursorIndexOfMName = CursorUtil.getColumnIndexOrThrow(cursor, "name"); final List<User> _res = new ArrayList<User>(cursor.getCount()); while(cursor.moveToNext()) { final User _item; _item = new User(); final int _tmpMId; _tmpMId = cursor.getInt(_cursorIndexOfMId); _item.setId(_tmpMId); final String _tmpMName; _tmpMName = cursor.getString(_cursorIndexOfMName); _item.setName(_tmpMName); _res.add(_item); } return _res; } }; } }; } 复制代码
在LimitOffsetDataSource
里,通过convertRows
的实现从数据库拿到list,
//LimitOffsetDataSource.java void loadInitial(LoadInitialParams params,LoadInitialCallback<T> callback) { //计算查询范围 sqLiteQuery = getSQLiteQuery(firstLoadPosition, firstLoadSize); //得到游标 cursor = mDb.query(sqLiteQuery); List<T> rows = convertRows(cursor); //拿到list后,就可以进行diff操作了 list = rows; callback.onResult(list, firstLoadPosition, totalCount); } 复制代码
本文使用 mdnice 排版