Android Paging数据刷新及原理解析

xiaoxiao2025-05-24  55

Paging

刷新数据以及解析

从刷新讲起

流程

Paging的确很好用,省了很多步骤,但跟以往简单的刷新不同的是,Paging比较麻烦,不能直接清空Adapter里面的数据了和直接请求第一页的数据。以前清除列表,再重新请求第一页数据就可以了。那么Paging如何刷新呢?大致上也和以前用法一样,无外乎就是清除数据并加载新数据

清除Adapter里面的数据

submitList()通过传入null值,如下源码:

public void submitList(final PagedList<T> pagedList) { ... // incrementing generation means any currently-running diffs are discarded when they finish final int runGeneration = ++mMaxScheduledGeneration; if (pagedList == null) { int removedCount = getItemCount(); if (mPagedList != null) { mPagedList.removeWeakCallback(mPagedListCallback); mPagedList = null; } else if (mSnapshot != null) { mSnapshot = null; } // dispatch update callback after updating mPagedList/mSnapshot mUpdateCallback.onRemoved(0, removedCount); if (mListener != null) { mListener.onCurrentListChanged(null); } return; } ... }

也就是在传入值为null的情况下,最终会执行

mUpdateCallback.onRemoved(0, removedCount);

ListUpdateCallback是一个接口,其实现类如下:

/** * ListUpdateCallback that dispatches update events to the given adapter. * * @see DiffUtil.DiffResult#dispatchUpdatesTo(RecyclerView.Adapter) */ public final class AdapterListUpdateCallback implements ListUpdateCallback { @NonNull private final RecyclerView.Adapter mAdapter; /** * Creates an AdapterListUpdateCallback that will dispatch update events to the given adapter. * * @param adapter The Adapter to send updates to. */ public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) { mAdapter = adapter; } /** {@inheritDoc} */ @Override public void onInserted(int position, int count) { mAdapter.notifyItemRangeInserted(position, count); } /** {@inheritDoc} */ @Override public void onRemoved(int position, int count) { mAdapter.notifyItemRangeRemoved(position, count); } /** {@inheritDoc} */ @Override public void onMoved(int fromPosition, int toPosition) { mAdapter.notifyItemMoved(fromPosition, toPosition); } /** {@inheritDoc} */ @Override public void onChanged(int position, int count, Object payload) { mAdapter.notifyItemRangeChanged(position, count, payload); } }

也就是onRemoved(0, removedCount)还是调用了RecyclerView.Adapter的notifyItemRangeRemoved(),因此,如果在submitList()传入null值,最终会让Adapter移除从位置0开始的getItemCount()个数据,即删除全部数据

请求第一页的数据

那么还有一个问题,就是加载第一页数据。Paging并没有根据页数请求数据的方法。即使接口是有提供这个方法,那么只能看DataSource的loadInitial()是在什么情况下被调用的了

在本项目中,DataSource继承自PageKeyedDataSource,查看在该类中loadInitial()被调用情况

@Override final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize, boolean enablePlaceholders, @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver) { LoadInitialCallbackImpl<Key, Value> callback = new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver); loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback); // If initialLoad's callback is not called within the body, we force any following calls // to post to the UI thread. This constructor may be run on a background thread, but // after constructor, mutation must happen on UI thread. callback.mCallbackHelper.setPostExecutor(mainThreadExecutor); }

在PageKeyedDataSource中只被调用一次,即dispatchLoadInitial(),dispatchLoadInitial()又是在ContiguousPagedList中的构造方法中被调用,即创建ContiguousPagedList时就开始请求第一页的数据

ContiguousPagedList( @NonNull ContiguousDataSource<K, V> dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, @Nullable BoundaryCallback<V> boundaryCallback, @NonNull Config config, final @Nullable K key, int lastLoad) { super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor, boundaryCallback, config); mDataSource = dataSource; mLastLoad = lastLoad; if (mDataSource.isInvalid()) { detach(); } else { mDataSource.dispatchLoadInitial(key, mConfig.initialLoadSizeHint, mConfig.pageSize, mConfig.enablePlaceholders, mMainThreadExecutor, mReceiver); } }

继续循着调用链往上找,就能看到是在PagedList的create()中开始去创建ContiguousPagedList实例

@NonNull private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource, @NonNull Executor notifyExecutor, @NonNull Executor fetchExecutor, @Nullable BoundaryCallback<T> boundaryCallback, @NonNull Config config, @Nullable K key) { if (dataSource.isContiguous() || !config.enablePlaceholders) { int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED; if (!dataSource.isContiguous()) { //noinspection unchecked dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource) .wrapAsContiguousWithoutPlaceholders(); if (key != null) { lastLoad = (int) key; } } ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource; return new ContiguousPagedList<>(contigDataSource, notifyExecutor, fetchExecutor, boundaryCallback, config, key, lastLoad); } else { return new TiledPagedList<>((PositionalDataSource<T>) dataSource, notifyExecutor, fetchExecutor, boundaryCallback, config, (key != null) ? (Integer) key : 0); } }

在代码中有两个条件,就能去创建ContiguousPagedList实例了

if (dataSource.isContiguous() || !config.enablePlaceholders) { return new ContiguousPagedList<>(contigDataSource, notifyExecutor, fetchExecutor, boundaryCallback, config, key, lastLoad); }

isContiguous()在DataSource是抽象方法。看下DataSource的Hierarchy,如图:

isContiguous()从字面上判断就是“是否为连续的”,而且在ContiguousDataSource类中就已经实现,他是永远返回true的

@Override boolean isContiguous() { return true; }

走到这一步就已经可以判断可以往下执行了,因为是或关系,一个为true就可以了,但是另一个条件:!config.enablePlaceholders,在创建GankDataViewModel实例中就已经通过setEnablePlaceholders(false)决定好了,它的值是false:

public GankDataViewModel() { init(); } private void init() { mExecutor = Executors.newFixedThreadPool(3); mFactory = new GankDataSourceFactory(); mPagedListConfig = (new PagedList.Config.Builder()) .setEnablePlaceholders(false)//配置是否启动PlaceHolders .setInitialLoadSizeHint(20)//初始化加载的数量 .setPageSize(10)//配置分页加载的数量 .build(); ... }

回到之前所讲的PagedList的create()这一步。create()是在PagedList的build()方法中调用,PagedList采用建造者模式,创建一个PagedList实例,PagedList也是一个集合类。此时重心转移到了PagedList初始化的时机上面。继续跟进,如下:

//LivePagedListBuilder类 @AnyThread @NonNull private static <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) { @Nullable private PagedList<Value> mList; @Nullable private DataSource<Key, Value> mDataSource; private final DataSource.InvalidatedCallback mCallback = new DataSource.InvalidatedCallback() { @Override public void onInvalidated() { invalidate(); } }; @Override protected PagedList<Value> compute() { @Nullable Key initializeKey = initialLoadKey; if (mList != null) { //noinspection unchecked initializeKey = (Key) mList.getLastKey(); } do { if (mDataSource != null) { mDataSource.removeInvalidatedCallback(mCallback); } mDataSource = dataSourceFactory.create(); mDataSource.addInvalidatedCallback(mCallback); mList = new PagedList.Builder<>(mDataSource, config) .setNotifyExecutor(notifyExecutor) .setFetchExecutor(fetchExecutor) .setBoundaryCallback(boundaryCallback) .setInitialKey(initializeKey) .build(); } while (mList.isDetached()); return mList; } }.getLiveData(); }

嗯,是创建了LiveData对象,但是什么时候调用compute()呢?毕竟是在这个方法中去创建PagedList,进而去调用loadInitial()请求数据

在LivePagedListBuilder.create()实际返回的是一个ComputableLiveData对象,这个类并不是LiveData的子类,而是内部有一个成员变量是LiveData类型的

public abstract class ComputableLiveData<T> { ... private final Executor mExecutor; private final LiveData<T> mLiveData; /** * Creates a computable live data that computes values on the arch IO thread executor. */ @SuppressWarnings("WeakerAccess") public ComputableLiveData() { this(ArchTaskExecutor.getIOThreadExecutor()); } /** * * Creates a computable live data that computes values on the specified executor. * * @param executor Executor that is used to compute new LiveData values. */ @SuppressWarnings("WeakerAccess") public ComputableLiveData(@NonNull Executor executor) { mExecutor = executor; mLiveData = new LiveData<T>() { @Override protected void onActive() { mExecutor.execute(mRefreshRunnable); } }; } @VisibleForTesting final Runnable mRefreshRunnable = new Runnable() { @WorkerThread @Override public void run() { boolean computed; do { computed = false; // compute can happen only in 1 thread but no reason to lock others. if (mComputing.compareAndSet(false, true)) { // as long as it is invalid, keep computing. try { T value = null; while (mInvalid.compareAndSet(true, false)) { computed = true; value = compute(); } if (computed) { mLiveData.postValue(value); } } finally { // release compute lock mComputing.set(false); } } // check invalid after releasing compute lock to avoid the following scenario. // Thread A runs compute() // Thread A checks invalid, it is false // Main thread sets invalid to true // Thread B runs, fails to acquire compute lock and skips // Thread A releases compute lock // We've left invalid in set state. The check below recovers. } while (computed && mInvalid.get()); } }; ... }

在初始化ComputableLiveData时也会创建LiveData对象,并重写了onActive(),使其在active状态下执行mRefreshRunnable任务,从而调用compute()和发出事件postValue(),这个事件在这里可以认为是刷新数据

已经创建了ComputableLiveData,也知道ComputableLiveData什么时候会去调用compute()去创建PagedList了,那么是谁调用LivePagedListBuilder.create()?是由同类的build()方法调用

@NonNull public LiveData<PagedList<Value>> build() { return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory, ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor); }

此时,答案终于明显了,在自定义ViewModel(GankDataViewModel)中的构造方法中也创建了LiveData对象监听列表数据变化,并submitList到Adapter中

//GankDataViewModel public GankDataViewModel() { init(); } private void init() { ... mPagedListLiveData = (new LivePagedListBuilder(mFactory, mPagedListConfig)) .setFetchExecutor(mExecutor) .build(); }

梳理

感觉逆着调用链去找好像漏了点,在此梳理下。不过为了防止重复,会简略很多

项目中通过LiveData监听数据变化,而LiveData是在ViewModel中创建的,这时候就需要创建ViewModel了,当然在Activity/Fragment中用来监听的LiveData和发送通知的LiveData需要是同一个

在创建ViewModel时,也会创建PagedList.Config对象和LiveData<PagedList<>>对象。创建LiveData<PagedList<>>是通过LivePagedListBuilder对象来得到的。通过链式调用执行到build()后,就返回LiveData<PagedList<>>对象了

//自定义ViewModel mPagedListLiveData = (new LivePagedListBuilder(mFactory, mPagedListConfig)) .setFetchExecutor(mExecutor) .build(); //LivePagedListBuilder类 @NonNull public LiveData<PagedList<Value>> build() { return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory, ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor); }

build()调用了其create()方法,该方法返回ComputableLiveData<PagedList<>>对象,并且在这个对象中,通过调用compute()就通过传入的dataSourceFactory调用重写的create()创建DataSource,及创建了PagedList对象

ComputableLiveData对象在创建时就会在传入的Activity等生命周期对象处理active状态下就会去调用compute(),由此创建了PagedList对象,当然返回的是PagedList子类ContiguousPagedList的实例,在其构造器中,将会调用此前创建的DataSource的dispatchLoadInitial(),进而调用loadInitial(),开始请求第一页的数据(PageKeyedDataSource)

总结

已经知道什么时候Paging去请求新数据了,那么如何刷新呢?重新创建LiveData<PagedList<>>就可以了

public LiveData<PagedList<FuliDataBean.ResultsBean>> getRefreshLiveData(){ mPagedListLiveData = (new LivePagedListBuilder(mFactory, mPagedListConfig)) .setFetchExecutor(mExecutor) .build(); return mPagedListLiveData; }

当然还有其他事情要做好

//MainActivity private void loadData() { mGankFuliDataViewModel = GankRepository.getGankFuliDataViewModel(this); mGankFuliDataViewModel.getGankFuliLiveData().observe(this, new Observer<PagedList<FuliDataBean.ResultsBean>>() { @Override public void onChanged(@Nullable PagedList<FuliDataBean.ResultsBean> resultsBeans) { mRefreshLayout.setRefreshing(false); mAdapter.submitList(resultsBeans); LogUtils.e("ExerciseClient_net",mAdapter.getItemCount()+""); } }); } @Override public void onRefresh() { mAdapter.submitList(null); mGankFuliDataViewModel.getRefreshLiveData().observe(this, new Observer<PagedList<FuliDataBean.ResultsBean>>() { @Override public void onChanged(@Nullable PagedList<FuliDataBean.ResultsBean> listBeans) { mRefreshLayout.setRefreshing(false); mAdapter.submitList(listBeans); } }); } //GankDataViewModel public LiveData<PagedList<FuliDataBean.ResultsBean>> getGankFuliLiveData() { return mPagedListLiveData; } public LiveData<PagedList<FuliDataBean.ResultsBean>> getRefreshLiveData(){ mPagedListLiveData = (new LivePagedListBuilder(mFactory, mPagedListConfig)) .setFetchExecutor(mExecutor) .build(); return mPagedListLiveData; }

注意:最好用同一个LiveData变量去引用

项目地址为ExerciseClient

转载请注明原文地址: https://www.6miu.com/read-5030624.html

最新回复(0)