本篇分析的是paging版本2.1.1的流程
因为jetpack目前更新频率较快,可能过几周这部分代码会有变动
1.分析
上篇的例子中的ViewModel创建方法为
class PersonViewModel : ViewModel {
private var liveData: LiveData<PagedList<Person>>
constructor() {
val factory = PersonFactory()
val config = PagedList.Config.Builder()
.setPageSize(pageSize)
.setInitialLoadSizeHint(pageSize)
.setEnablePlaceholders(false)
.setPrefetchDistance(5)
.build();
liveData = LivePagedListBuilder<Int, Person>(factory, config).build()
}
fun getPersonLiveData(): LiveData<PagedList<Person>> {
return liveData
}
inner class PersonFactory : DataSource.Factory<Int, Person>() {
override fun create(): DataSource<Int, Person> {
// return PersonDataSource()
// return PersonDataPageKeySource()
return PersonDataItemKeySource()
}
}
}
首先会创建一个LiveData<PagedList< Person >>,之后回监听LiveData变化,发生变化再用
public void submitList(@Nullable PagedList<T> pagedList)
这个方法通知适配器
private Executor mFetchExecutor = ArchTaskExecutor.getIOThreadExecutor();
public LiveData<PagedList<Value>> build() {
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
}
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();
}
};
@SuppressWarnings("unchecked") // for casting getLastKey to Key
@Override
protected PagedList<Value> compute() {
@Nullable Key initializeKey = initialLoadKey;
if (mList != null) {
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();
}
可以看出这里会创建一个ComputableLiveData<PagedList< Value >>,然后拿到里面的LiveData对象;这里重写了compute方法,然后拿到传入的DataSourceFactory,从其中的create方法获取到DataSource,并给这个DataSource设置一个InvalidatedCallback
然后会创建一个PagedList,然后会循环判断有效性,如果是有效的那么直接返回;这里的的有效性主要是检查的DataSource的有效性,在PageList的创建过程中会去检查DataSource,如果已经失效了,那么会把当前要创建的DataSource标记为detach,在这里就要重新创建一遍
ComputableLiveData的构造方法中传入了一个执行器fetchExecutor,从初始化方法看应该是会用IO线程处理逻辑
这里的DataSourceFactory就是 我们定义的那个,在里面返回我们自定义的那三种数据操作DataSource
然后看一下ComputableLiveData的构造方法
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData<T>() {
@Override
protected void onActive() {
mExecutor.execute(mRefreshRunnable);
}
};
}
这里用的是上面的那个IO处理的Executor,然后创建了一个LiveData,重写了onActive方法,从之前的LiveData分析中可以知道,这是一个从非活跃切换到活跃状态会执行的方法(首次创建进入也是会执行的),来看下这个Runnable
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());
}
};
这里有两个原子操作,一个是保证整个执行期间的原子性,执行完在finally直接重新设置回去,而另一个mInvalid是检查有效性的,如果执行期间正常,那么就不会重新调用compute方法,如果某个时间点出现问题失效了那么就会重新创建DataSource和相应的PageList
目前发现的重新设置为无效的方法在TiledDataSource中,而这个类已经被打上了Deprecated标记,后面也不会讨论这个DataSource,所以暂时可以认为这个失效性判断是没有的, 首次执行完就会变成false,也就是说后续一直有效
回到上面的onActive方法,测试中,每当屏幕从熄屏变为亮屏时,这个方法都会执行,而后续断点到mInvalid这里时,一直都没有执行这里的逻辑,也就是正常情况是只有第一次会执行并发送postValue通知
也就是说我们注册的observer也就只有第一次会通知我们创建了 PagedList< Person > 这个,然后我们把这个和adapter绑定后,后续都不会接收到这个通知了,比如我在之前的例子onChange方法加一个打印信息
E/1234: loadInitial
E/1234: onChanged pageListReceive -> []
E/1234: loadAfter 9
E/1234: loadAfter 19
E/1234: loadAfter 29
E/1234: loadAfter 39
E/1234: loadAfter 49
会发现后面分页都不会再走这个监听,所以这个仅仅是一个绑定的作用
回到第一个方法中,会创建一个PagedList对象,build方法最后会调用到create方法
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 = (Integer) 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);
}
}
这里会根据情况划分两种PageLiset,有两种条件判断
isContiguous 只有PositionalDataSource是true,另外两个都是true
enablePlaceholders 默认是true,就是允许null占位补齐,而我们的需求一般都是会把这个给禁用掉,也就是设置成false
所以这个方法我们主要看第一个ContiguousPagedList
ContiguousPagedList(
@NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
@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);
}
mShouldTrim = mDataSource.supportsPageDropping()
&& mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED;
}
可以看到这里进行了isInvalid检查,现在一般默认都是false的,然后马上调用dispatchLoadInitial方法分发初始化,而这个方法是个抽象方法
abstract void dispatchLoadInitial(
@Nullable Key key,
int initialLoadSize,
int pageSize,
boolean enablePlaceholders,
@NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver);
也就是交付给子类处理,比如我们查看一下PositionalDataSource内的实现
final void dispatchLoadInitial(boolean acceptCount,
int requestedStartPosition, int requestedLoadSize, int pageSize,
@NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
LoadInitialCallbackImpl<T> callback =
new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);
LoadInitialParams params = new LoadInitialParams(
requestedStartPosition, requestedLoadSize, pageSize, acceptCount);
loadInitial(params, 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);
}
这里会把传过来的初始化需求配置用LoadInitialParams包装,然后调用loadInitial方法,而这个方法也是抽象的,也正是我们需要实现的方法
public abstract void loadInitial(
@NonNull LoadInitialParams params,
@NonNull LoadInitialCallback<T> callback);
然后我们数据处理完毕后会调用callback.onResult(…)方法把我们的数据集合传递回去,而这个callback是上面刚创建的LoadInitialCallbackImpl
这个onResult方法经过处理后会到DataSource的dispatchOnCurrentThread方法内
void dispatchOnCurrentThread(@Nullable PageResult<T> result,
@Nullable Throwable error, boolean retryable) {
if (result != null) {
mReceiver.onPageResult(mResultType, result);
} else {
mReceiver.onPageError(mResultType, error, retryable);
}
}
而这里的mReceiver是在ContiguousPagedList中创建的
PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
@AnyThread
@Override
public void onPageResult(@PageResult.ResultType int resultType,
@NonNull PageResult<V> pageResult) {
if (pageResult.isInvalid()) {
detach();
return;
}
if (isDetached()) {
return;
}
List<V> page = pageResult.page;
if (resultType == PageResult.INIT) {
mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
pageResult.positionOffset, ContiguousPagedList.this);
if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
mLastLoad =
pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
}
} else {
boolean trimFromFront = mLastLoad > mStorage.getMiddleOfLoadedRange();
boolean skipNewPage = mShouldTrim
&& mStorage.shouldPreTrimNewPage(
mConfig.maxSize, mRequiredRemainder, page.size());
if (resultType == PageResult.APPEND) {
if (skipNewPage && !trimFromFront) {
mAppendItemsRequested = 0;
mLoadStateManager.setState(LoadType.END, LoadState.IDLE, null);
} else {
mStorage.appendPage(page, ContiguousPagedList.this);
}
} else if (resultType == PageResult.PREPEND) {
if (skipNewPage && trimFromFront) {
mPrependItemsRequested = 0;
mLoadStateManager.setState(LoadType.START, LoadState.IDLE, null);
} else {
mStorage.prependPage(page, ContiguousPagedList.this);
}
} else {
throw new IllegalArgumentException("unexpected resultType " + resultType);
}
......
}
@Override
public void onPageError(@PageResult.ResultType int resultType,
@NonNull Throwable error, boolean retryable) {
LoadState errorState = retryable ? LoadState.RETRYABLE_ERROR : LoadState.ERROR;
if (resultType == PageResult.PREPEND) {
mLoadStateManager.setState(LoadType.START, errorState, error);
} else if (resultType == PageResult.APPEND) {
mLoadStateManager.setState(LoadType.END, errorState, error);
} else {
// TODO: pass init signal through to *previous* list
throw new IllegalStateException("TODO");
}
}
};
比如初始化会进入resultType == PageResult.INIT这个判断,然后调用 mStorage.init(…)方法,而这init方法会做一些数据初始化
void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
@NonNull Callback callback) {
init(leadingNulls, page, trailingNulls, positionOffset);
callback.onInitialized(size());
}
private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) {
mLeadingNullCount = leadingNulls;
mPages.clear();
mPages.add(page);
mTrailingNullCount = trailingNulls;
mPositionOffset = positionOffset;
mLoadedCount = page.size();
mStorageCount = mLoadedCount;
mPageSize = page.size();
mNumberPrepended = 0;
mNumberAppended = 0;
}
然后会回调callback.onInitialized,通知此时的数据大小
public void onInitialized(int count) {
notifyInserted(0, count);
mReplacePagesWithNulls =
mStorage.getLeadingNullCount() > 0 || mStorage.getTrailingNullCount() > 0;
}
private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
void notifyInserted(int position, int count) {
if (count != 0) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
final Callback callback = mCallbacks.get(i).get();
if (callback != null) {
callback.onInserted(position, count);
}
}
}
}
在这个方法里最终会遍历注册的callbacks去循环通知有数据插入,那么现在就是在于这个callback是什么了
然后回到我们适配器的创建,上篇的例子中
class PaginAdapter : PagedListAdapter<Person, PaginAdapter.MViewHolder> {
constructor() : super(MDiffCallBack())
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MViewHolder {
......
}
override fun onBindViewHolder(holder: MViewHolder, position: Int) {
......
}
inner class MViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
......
}
class MDiffCallBack : DiffUtil.ItemCallback<Person>() {
override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
return oldItem.name == (newItem.name) && oldItem.positon == newItem.positon;
}
override fun areContentsTheSame(oldItem: Person, newItem: Person): Boolean {
return oldItem.name == newItem.name;
}
}
和正常适配器用法基本一致,不同的是适配器的继承改为了PagedListAdapter,而这个PagedListAdapter本身也是继承RecyclerView.Adapter的,然后就是构造方法需要传入一个DiffUtil.ItemCallback,看一下构造方法
protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
mDiffer.addPagedListListener(mListener);
mDiffer.addLoadStateListener(mLoadStateListener);
}
public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mUpdateCallback = new AdapterListUpdateCallback(adapter);
mConfig = new AsyncDifferConfig.Builder<>(diffCallback).build();
}
PageListAdapter会创建一个AsyncPagedListDiffer,而后者的构造方法中会把当前的PageListAdapter传进去,并且会创建一个AdapterListUpdateCallback,而这个callBack正是刷新适配器数据的回调
public final class AdapterListUpdateCallback implements ListUpdateCallback {
@NonNull
private final RecyclerView.Adapter mAdapter;
public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) {
mAdapter = adapter;
}
@Override
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mAdapter.notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
mAdapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
mAdapter.notifyItemRangeChanged(position, count, payload);
}
}
然后我们需要打通这些的联系,而这个方法正是submitList
public void submitList(@Nullable final PagedList<T> pagedList,
@Nullable final Runnable commitCallback) {
......
if (mPagedList == null && mSnapshot == null) {
// fast simple first insert
mPagedList = pagedList;
mPagedList.addWeakLoadStateListener(mLoadStateListener);
pagedList.addWeakCallback(null, mPagedListCallback);
mUpdateCallback.onInserted(0, pagedList.size());
onCurrentListChanged(null, pagedList, commitCallback);
return;
}
......
}
submitList方法进过几轮调用会走到上面的方法中,而pageList.addWeakCallBack正是添加callback数据的那个方法,而且这里会马上调用一次UpdateCallback.onInserted方法去刷新适配器,因为绑定adater和pagelist已经首次数据请求并不是顺序执行的;绑定适配器关系由用户控制,可能无限期滞后,所以首次绑定可能造成已经有数据,但是页面没有刷新的情况,所以这里强制检查刷新一遍
public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) {
if (previousSnapshot != null && previousSnapshot != this) {
......
mCallbacks.add(new WeakReference<>(callback));
}
然后注意一下上面的mPagedListCallback,同步回调了mUpdateCallback的方法,也就是刷新适配器的方法
private PagedList.Callback mPagedListCallback = new PagedList.Callback() {
@Override
public void onInserted(int position, int count) {
mUpdateCallback.onInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mUpdateCallback.onRemoved(position, count);
}
@Override
public void onChanged(int position, int count) {
// NOTE: pass a null payload to convey null -> item
mUpdateCallback.onChanged(position, count, null);
}
};
这样整个初始化数据流程就走完了
然后是加载更多的逻辑,与普通的用法不同,适配器在bind方法中需要通过getItem方法获取数据对象
override fun onBindViewHolder(holder: MViewHolder, position: Int) {
val person = getItem(position)
person?.apply {
holder.dataBinding?.person = person
holder.dataBinding?.executePendingBindings()
}
}
那么切入点就在这
public T getItem(int index) {
if (mPagedList == null) {
if (mSnapshot == null) {
throw new IndexOutOfBoundsException(
"Item count is zero, getItem() call is invalid");
} else {
return mSnapshot.get(index);
}
}
mPagedList.loadAround(index);
return mPagedList.get(index);
}
getItem方法会进入到pagelist得到loadAround方法中,然后会走到子类ContiguousPagedList中,这里是以我们的例子走的流程,可能有其他分支
protected void loadAroundInternal(int index) {
int prependItems = getPrependItemsRequested(mConfig.prefetchDistance, index,
mStorage.getLeadingNullCount());
int appendItems = getAppendItemsRequested(mConfig.prefetchDistance, index,
mStorage.getLeadingNullCount() + mStorage.getStorageCount());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0 && mLoadStateManager.getStart() == LoadState.IDLE) {
schedulePrepend();
}
mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
if (mAppendItemsRequested > 0 && mLoadStateManager.getEnd() == LoadState.IDLE) {
scheduleAppend();
}
}
static int getAppendItemsRequested(
int prefetchDistance, int index, int itemsBeforeTrailingNulls) {
return index + prefetchDistance + 1 - itemsBeforeTrailingNulls;
}
这里会计算出从什么位置开始去加载下一页
就是 当前位置index + 配置的加载偏移量 + 1 > 当前数据的总长度 即是下一页加载的条件
也就是当滑动到最大数量 - 1 - 偏移量时候开始加载下一页,比如有10条数据,匹配的偏移量是5,当getITtem获取到索引是4的数据时候就会去加载下一页
private void scheduleAppend() {
mLoadStateManager.setState(LoadType.END, LoadState.LOADING, null);
final int position = mStorage.getLeadingNullCount()
+ mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
// safe to access first item here - mStorage can't be empty if we're appending
final V item = mStorage.getLastLoadedItem();
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (isDetached()) {
return;
}
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
mMainThreadExecutor, mReceiver);
}
}
});
}
scheduleAppend方法会把当前的End标记修改为LOADING,加载期间再怎么滑到底部也不会执行
dispatchLoadAfter和上面init方法类似,最后都交付我们自己处理,再通过Result传给我们,然后在通知的方法中
@MainThread
@Override
public void onEmptyAppend() {
mLoadStateManager.setState(LoadType.END, LoadState.DONE, null);
}
@MainThread
@Override
public void onPageAppended(int endPosition, int changedCount, int addedCount) {
// consider whether to post more work, now that a page is fully appended
mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
if (mAppendItemsRequested > 0) {
// not done appending, keep going
scheduleAppend();
} else {
mLoadStateManager.setState(LoadType.END, LoadState.IDLE, null);
}
// finally dispatch callbacks, after append may have already been scheduled
notifyChanged(endPosition, changedCount);
notifyInserted(endPosition + changedCount, addedCount);
}
下一页加载完成,如果发现下下一页的条件还是满足的,那么会继续加载下下一页,否则会把当前End标记修改为IDLE
如果没有数据Empty就会把End修改为DONE,后续也就不再执行
至此,整个Paging的流程就分析完毕了,流程图大致为
2.总结
1.LivePagedListBuilder的build方法会马上返回LiveData<PagedList< T >>一个对象,但内部的PageList并没有创建,需要等待页面活跃后才会创建
2.PageList创建后会马上通过dispatchLoadInitial方法去通知执行初始化数据方法,这里是交付给我们自定义的DataSources的loadInitial方法处理
3.处理完数据后通过callback.result方法传递数据信息,这个会经过一层层回调最终到PagedList.Callback中,去执行适配器的刷新方法
4.加载更多的判断是在getItem方法中,检测到满足条件的索引条件后会去执行加载下一页,修改当前的State属性,并把加载下一页的逻辑交付我们自定义的DataSource的loadAfter方法中;然后加载数据处理完毕后,再通过层层回调最后通知刷新适配器,并把State属性修改为原本属性或者设置成完成状态
5.首次PageList和适配器绑定会强制刷新一遍全部数据
6.PageList的监听方法只会回调一次,也就是绑定适配器也只是只有一遍