从刷新讲起
Paging的
确很好用,省了很多步骤,但跟以往简单的刷新不同的是,Paging
比较麻烦,不能直接清空Adapter
里面的数据了和直接请求第一页的数据。以前清除列表,再重新请求第一页数据就可以了。那么Paging
如何刷新呢?大致上也和以前用法一样,无外乎就是清除数据并加载新数据
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
对象。创建LiveData
是通过LivePagedListBuilder
对象来得到的。通过链式调用执行到build()
后,就返回LiveData
对象了
//自定义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
对象,并且在这个对象中,通过调用compute()
就通过传入的dataSourceFactory
调用重写的create()
创建DataSource
,及创建了PagedList
对象
ComputableLiveData
对象在创建时就会在传入的Activity
等生命周期对象处理active
状态下就会去调用compute()
,由此创建了PagedList
对象,当然返回的是PagedList
子类ContiguousPagedList
的实例,在其构造器中,将会调用此前创建的DataSource
的dispatchLoadInitial()
,进而调用loadInitial()
,开始请求第一页的数据(PageKeyedDataSource
)
已经知道什么时候Paging去请求新数据了,那么如何刷新呢?重新创建LiveData
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