入口
从使用角度分析,从LivePagedListBuilder开始
创建LivePagedListBuilder代码示例:
val sourceFactory = FeedListDataSourceFactory(NETWORK_IO)
val livePagedList = LivePagedListBuilder(sourceFactory, 10)
.setBoundaryCallback(FeedlistBoundaryCallback())
.setFetchExecutor(NETWORK_IO)
.build()
--> 进入build()方法
@NonNull
public LiveData> build() {
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
}
创建并返回 ComputableLiveData的.getLiveData()调用
private static LiveData> create(...) {
return new ComputableLiveData>(fetchExecutor) {
...
@Override
protected PagedList compute() {
...
return mList;
}
}.getLiveData();
具体看一下其返回的LiveData定义: 也就是mLiveData
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData() {
@Override
protected void onActive() {
mExecutor.execute(mRefreshRunnable);
}
};
}
看到, 当有observer关注这个实例时, 会触发执行mRefreshRunnable.
而这个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());
}
};
使用mComputing变量来实现一个简单独占锁, 并且不block其他线程.
如果进行了计算(computed为true ) 就更新mLiveData中的值. 这个mLiveData就是最前面通过LivePagedListBuilder的build()方法返回的那个liveData实例.
所以可以先简单认为执行一次mRefreshRunnable, 就会更新一次liveData的值, ui层的observer就会得到更新的通知.
到这里初始化看似就结束了. 但还漏了一步, 我们自定义的DataSource会自动执行loadInitial()方法. 这是怎么执行流程呢?
下面分析下.
触发loadInitial()的入口是UI层对liveData的监听, 示例如下:
model.topicList.observe(this, Observer> { topics ->
Log.d(TAG, "observe. $topics")
if (topics != null && topics.size > 0) {
progressBar.visibility = View.INVISIBLE
}
adapter.submitList(topics)
})
前面说过, 当进行observe监听设置的时候, 会触发mRefreshRunnable执行.
而在mRefreshRunnable执行逻辑中又会执行compute(); 看看这个方法的实现:
return new ComputableLiveData>(fetchExecutor) {
@Nullable
private PagedList mList;
@Nullable
private DataSource mDataSource;
private final DataSource.InvalidatedCallback mCallback =
new DataSource.InvalidatedCallback() {
@Override
public void onInvalidated() {
invalidate();
}
};
@Override
protected PagedList 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();
关注其中的compute()方法, 首先是生成mDataSource对象, 然后再生成mList并返回, 这个返回的mList就是返回给UI层监听器的具体数据.
所以重点就是mList的创建过程了. 又是一个眼熟的build()构建方法.
PageList.java
@NonNull
private static PagedList create(@NonNull DataSource dataSource,
@NonNull Executor notifyExecutor,
@NonNull Executor fetchExecutor,
@Nullable BoundaryCallback 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) ((PositionalDataSource) dataSource)
.wrapAsContiguousWithoutPlaceholders();
if (key != null) {
lastLoad = (Integer) key;
}
}
ContiguousDataSource contigDataSource = (ContiguousDataSource) dataSource;
return new ContiguousPagedList<>(contigDataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
key,
lastLoad);
} else {
return new TiledPagedList<>((PositionalDataSource) dataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
(key != null) ? (Integer) key : 0);
}
}
看到这里, 有个问题: ContiguousPagedList和TiledPagedList各自是什么语义?
不知道. 简单说的话, ItemKeyedDataSource和PageKeyedDataSource这两种datasource对应的是ContiguousPagedList
而 PositionalDataSource对应的是TiledPagedList.
反推过来, 自己理解吧.
我们这里分析的是ContiguousPagedList. 所以进入其构造方法.
ContiguousPagedList(
@NonNull ContiguousDataSource dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback boundaryCallback,
@NonNull Config config,
final @Nullable K key,
int lastLoad) {
super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
mLastLoad = lastLoad;
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
}
}
在这里面我们就看到了调用 mDataSource.dispatchLoadInitial(). 再下一步就是loadInitial()了.
至于这句判断:
if (mDataSource.isInvalid()) {
detach();
}
想不明白, 什么时候会出现这种场景. 先不管吧.
进入PageKeyedDataSource.java 看看其dispatchLoadInitial()实现
@Override
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver receiver) {
LoadInitialCallbackImpl callback =
new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
loadInitial(new LoadInitialParams(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);
}
这里这句注释很重要, 但是我还是没有理解. 谁能给说说, 他到底是什么目的设计成这个样子. 这不是bug吗?
一定要在前面的oadInitial(new LoadInitialParams
(initialLoadSize, enablePlaceholders), callback);调用中进行同步加载, 异步的话你会倒霉. 也就是千万不要再在其中使用rxjava那种切换线程的方式去加载数据了, 直接在当前线程加载并返回给callback就好了. 此处浪费我将近5个小时调试. 之所以写这篇分析, 就是为了记录这个问题.
好了 流程先分析到这里. 以后有精力了再写.
还有一个问题:
如果我设置了BoundaryCallback, 那什么情况下才会触发?
val livePagedList = LivePagedListBuilder(sourceFactory, 10)
.setBoundaryCallback(FeedlistBoundaryCallback())
.setFetchExecutor(NETWORK_IO)
.build()
答: 对于通过loadAfter加载数据的模式, 只有在loadAfter返回0条数据, 并且滑到底部时才认为是到达边界了, 这两个条件必须同时满足. 也就是说, 单纯的滑动到底部, 并不会触发BoundaryCallback的onItemAtEndLoaded()回调.