PagedList用法和原理总结

什么是PagedList

PagedList是Google官方推出的一个分页加载库,即PagedList Library。这个库较完善的支持了“分页加载数据”这个常见的应用场景。PagedList一端可以无缝衔接RecyclerView,另一端可以从本地数据库或者网络获取数据源。

组成和基本原理

PagedList库主要由三部分组成:

  • DataSource:管理数据源
  • PagedList:封装后的分页数据
  • PagedListAdapter:代替RecyclerAdapter的适配器

其中,DataSource用来管理数据源。数据可能是来自本地数据库,也可能直接来自网络,总之最后都是要交给DataSource来管理的。PagedList是具体的分页数据,在DataSouce产出一组数据后,会封装为PagedList对象。PagedListAdapter可以直接代替RecyclerAdapter,在有新的PagedList对象到泪后,交给PagedListAdapter来处理,在内部进行数据的对比分析后,再更新RecyclerView列表。

DataSource

DataSource是数据源管理类,完美需要实现他的一些接口,在这些接口回调时从网络或者本地数据库获取数据。DataSource有三个现成的实现类可以直接用:

  • PositionalDataSource:适用于数据总量已知且不可变,我们从中直接查询指定位置的部分数据。(也不是说完全不可变,只是总量变化时会有很多问题需要额外处理)
  • PageKeyedDataSource:以页为单位加载任意数量的数据,这里的key有页码的意思,即每次加载的都是指定页码的数据
  • ItemKeyedDataSource:同样以页为单位加载任务数量的数据,但这里的key不是页码,而是每一页请求所需要的一个条件,每次加载的都是满足该条件的下一页数据

此外如果本地数据库用了Room,也可以自动生成一个DataSouce,不用我们手动创建。只需要在定义Room接口的时候返回一个 DataSource.Factory ,而Room的自动生成的DataSource是继承自PositionalDataSource的。

另外这里有一点,PositionalDataSource是跟另外两者完全不同的,另外两个是直接继承自ContigugousDataSource的,在源码中可以看到是“连续”的,这里的连续的意思是,每一页的数据一定依赖于上一页的数据,所以数据是有连续性的;而PositionalDataSource的数据表面上看也是一页跟一页的,实际上他每一页的数据只依赖于一个索引,这个索引和第一页、第二页不是一个概念,而是指在全部数据中的位置,类似于目录中的具体页码。

PagedList

PagedList是数据封装类,从DataSource,经过封装后成为PagedList对象。PagedList可以配置分页加载时每一页加载的数量,初始化时第一页加载的数量,加载下一页的阈值等。此外还可以添加一些回调,在数据更新、加载时会触发,比较重要的一个是 BoundaryCallback ,这个回调在一页数据加载完需要下一页时会触发。一个常见的场景是,数据来源虽然是网络,但是在本地会有一个缓存,网络数据会先在本地缓存,而后列表页从本地缓存中读取数据。这种情况下,BoundaryCallback 就变得尤为重要,我们可以在把 DataSource 设置为来自本地缓存,在每一页数据加载完触发回调时从网络获取下一页数据,成功获取到数据后把新的数据写入到本地缓存中。而本地缓存读取时用 LiveData ,这样只要缓存有更新,就会触发页面数据的更新。

PagedListAdapter

PagedListAdapter是用来代替RecylerAdapter的,创建时需要提供一个 DiffUtil.ItemCallback 对象,这是Adapter的核心。在有新数据添加到Adapter中时,内部会用这个对象来计算到底哪些item需要更新,之后再更新整个RecyclerView。

源码简析

一个简单的业务模型

这里以一个常用的业务模型流程为例来分析PagedList到底做了什么。这个业务模型很简单,就是首先数据来自于网络(Retrofit),从网络获取到数据后,直接写入数据库(Room),之后从数据库获取一个DataSource,来作为数据源,这样每当数据库有更新时,都会通过LiveData来告知PagedListAdapter去更新当前的PagedList数据。代码上大概就是:

首先是用Room来创建一个DataSource的获取:

1
2
3
4
5
6
7
8
9
@Dao
interface FeedDao {
@Query("SELECT * FROM feed ")
fun query(): DataSource.Factory<Int, Feed>
}

//...

val factory = db.feedDao().query()

再创建一个分页回调,这个回调在数据库为空或者读到头需要新数据时会回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
class FeedBoundaryCallback() : PagedList.BoundaryCallback<Feed>() {

override fun onZeroItemsLoaded() {
}

override fun onItemAtEndLoaded(itemAtEnd: Feed) {
}

}

//...

val feedBoundaryCallback = FeedBoundaryCallback()

再创建 LiveData<PagedList> :

1
val list = LivePagedListBuilder<Int, Feed>(factory, 20).setBoundaryCallback(feedBoundaryCallback).build()

最后监听上面的LiveData,在有新的数据到来时调用 submit

1
2
3
list.observe(this, Observer {
mFeedListAdapter.submitList(it)
})

接下来就以这个模型来简单分析一下背后的实现原理。

DataSource

第一步先来看看DataSouce是如何创建的。因为使用了Room,Room会自动生成DataSource的代码,我们无需写具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public DataSource.Factory<Integer, Feed> query(final String userId, final String feedType) {
final String _sql = "SELECT * FROM feed";
//......
return new DataSource.Factory<Integer, Feed>() {
@Override
public LimitOffsetDataSource<Feed> create() {
return new LimitOffsetDataSource<Feed>(__db, _statement, false , "feed") {
//......
}
};
}
}

query 方法返回了一个DataSource.Factory,重写了 create() 方法并返回了一个 LimitOffsetDataSource 实例,看下这是什么东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public abstract class LimitOffsetDataSource<T> extends PositionalDataSource<T> {

private final InvalidationTracker.Observer mObserver;
//...

protected LimitOffsetDataSource(RoomDatabase db, SupportSQLiteQuery query,
boolean inTransaction, String... tables) {
this(db, RoomSQLiteQuery.copyFrom(query), inTransaction, tables);
}

protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query,
boolean inTransaction, String... tables) {
//...
//这里主要是监听了数据库的刷新回调,在数据库刷新时同步调 invalidate()方法来刷新DataSource
mObserver = new InvalidationTracker.Observer(tables) {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
invalidate();
}
};
db.getInvalidationTracker().addWeakObserver(mObserver);
}
//...

}

LivePagedListBuilder

接下来看看 LiveData<PagedList> 是如何创建的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//在我们的代码中是调了build()方法来创建的
public LiveData<PagedList<Value>> build() {
//拿所有配置好的参数,调了create()方法
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;

//一个回调,被回调到时会调用内部的invalidate()方法
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);
}
//调用了前面那个DataSouce.Factory的create()方法重新创建了一个DataSouce,并添加了回调,这样数据库刷新->通知LiveData调用invalidate()方法
mDataSource = dataSourceFactory.create();
mDataSource.addInvalidatedCallback(mCallback);

//实际创建一个PagedList
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
} while (mList.isDetached());
return mList;
}
}.getLiveData();
}

这里是创建了一个 ComputableLiveData ,这又是什么呢

ComputableLiveData

这个LiveData我们平常极少会用到,看下他的源码注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* A LiveData class that can be invalidated & computed when there are active observers.
* <p>
* It can be invalidated via {@link #invalidate()}, which will result in a call to
* {@link #compute()} if there are active observers (or when they start observing)
* <p>
* This is an internal class for now, might be public if we see the necessity.
*
* @param <T> The type of the live data
* @hide internal
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public abstract class ComputableLiveData<T> {}

翻译一下就是这是一个在被订阅时才会开始计算;另外也提供了主动计算的方法。他的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
public ComputableLiveData(@NonNull Executor executor) {
//构建使用的线程池,默认是IO线程
mExecutor = executor;
//实际用的LiveData
mLiveData = new LiveData<T>() {
@Override
protected void onActive() {
//在被订阅时才会执行mRefreshRunnable的代码
mExecutor.execute(mRefreshRunnable);
}
};
}

再看看 mRefreshRunnable 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

@VisibleForTesting
final Runnable mRefreshRunnable = new Runnable() {
@WorkerThread
@Override
public void run() {
//标识是否已经计算完毕
boolean computed;
do {
//开始计算前,先为false,这样就一直在循环中了
computed = false;
// compute can happen only in 1 thread but no reason to lock others.
//标识是否正在进行计算,如果正在计算中,就直接跳过了;只有未进行计算时,才会改为true并开始计算
if (mComputing.compareAndSet(false, true)) {
// as long as it is invalid, keep computing.
try {
T value = null;
//mInvalid标识是否失效,失效时才会进行计算
while (mInvalid.compareAndSet(true, false)) {
computed = true;
//compute()方法会返回一个最新的计算结果
value = compute();
}
if (computed) {
//最新的计算结果,交给mLiveData,有点刷新的意思
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());
}
};

此外还提供了主动申请计算的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// invalidation check always happens on the main thread
@VisibleForTesting
final Runnable mInvalidationRunnable = new Runnable() {
@MainThread
@Override
public void run() {
//在主动重新计算时,是将mInvalid置为true,表示数据已经失效,然后去执行mRefreshRunnable
boolean isActive = mLiveData.hasActiveObservers();
if (mInvalid.compareAndSet(false, true)) {
if (isActive) {
mExecutor.execute(mRefreshRunnable);
}
}
}
};

/**
* Invalidates the LiveData.
* <p>
* When there are active observers, this will trigger a call to {@link #compute()}.
*/
//这个方法是对外提供的
public void invalidate() {
ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
}

最后,最终获取计算结果的方法:

1
2
@WorkerThread
protected abstract T compute();

简单总结一下,这个LiveData内部通过保存另一个实际的LiveData实例,在其被订阅时,或者是主动申请时,才会获取最新的计算结果,将其通过PostValue方法交给内部的LiveData。因为涉及到多线程,所以用了一些列AtomicBoolean变量来作为锁,保证线程安全。

PagedList的创建

前面铺垫了那么多,最后还是要创建PagedList,还是用了构建者模式:

1
2
3
4
5
6
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();

来看看 build() 方法做了什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public PagedList<Value> build() {
//noinspection unchecked
return PagedList.create(
mDataSource,
mNotifyExecutor,
mFetchExecutor,
mBoundaryCallback,
mConfig,
mInitialKey);
}

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) {
//根据dataSource是否连续,会创建两个不同的PagedList
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;
}
}
//连续会创建一个ContiguousPagedList
ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
return new ContiguousPagedList<>(contigDataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
key,
lastLoad);
} else {
//非连续会创建一个TiledPagedList
return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
(key != null) ? (Integer) key : 0);
}
}

这里会根据当前DataSouce的类型,来创建对应的不同的PagedList。

PagedListAdapter

最后再看看 PagedListAdapter 里面做了什么。

首先看创建这里,我们会传一个 DiffUtil.ItemCallback 的对象到构造器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
//这是核心
final AsyncPagedListDiffer<T> mDiffer;
//一个监听回调
private final AsyncPagedListDiffer.PagedListListener<T> mListener =
new AsyncPagedListDiffer.PagedListListener<T>() {
@Override
public void onCurrentListChanged(
@Nullable PagedList<T> previousList, @Nullable PagedList<T> currentList) {
//每当列表数据改变时都会回调这两个方法,这两个也是我们可以自己复写的
PagedListAdapter.this.onCurrentListChanged(currentList);
PagedListAdapter.this.onCurrentListChanged(previousList, currentList);
}
};

protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
//我们传进来的 DIffUtil.ItemCallback对象在这里作为创建 AsyncPagedListDiffer 对象的参数,同时加了前面那个回调
mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
mDiffer.addPagedListListener(mListener);
}
}

之后,我们会在监听到 LiveData 回调时,调用这个 Adaptersubmit() 方法把最新的 PagedList 传过去:

1
2
3
4
5
6
7
public void submitList(@Nullable PagedList<T> pagedList) {
mDiffer.submitList(pagedList);
}

public void submitList(@Nullable final PagedList<T> pagedList) {
submitList(pagedList, null);
}

一层一层调用,最后还是这个 submit() 的具体实现还是在 AsyncPagedListDiffer 内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public void submitList(@Nullable final PagedList<T> pagedList,
@Nullable final Runnable commitCallback) {
if (pagedList != null) {
if (mPagedList == null && mSnapshot == null) {
mIsContiguous = pagedList.isContiguous();
} else {
if (pagedList.isContiguous() != mIsContiguous) {
throw new IllegalArgumentException("AsyncPagedListDiffer cannot handle both"
+ " contiguous and non-contiguous lists.");
}
}
}

// incrementing generation means any currently-running diffs are discarded when they finish
final int runGeneration = ++mMaxScheduledGeneration;
//新加进来的pagedList和旧的mPagedList一样,就没必要更新了
if (pagedList == mPagedList) {
// nothing to do (Note - still had to inc generation, since may have ongoing work)
if (commitCallback != null) {
commitCallback.run();
}
return;
}

//mSnapshot就是mPagedList的快照,两个一样
final PagedList<T> previous = (mSnapshot != null) ? mSnapshot : mPagedList;

//新的pagedList为null 就要把原数据清空了
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);
onCurrentListChanged(previous, null, commitCallback);
return;
}

//旧 mPagedList 为null,就直接加载新数据
if (mPagedList == null && mSnapshot == null) {
// fast simple first insert
mPagedList = pagedList;
pagedList.addWeakCallback(null, mPagedListCallback);

// dispatch update callback after updating mPagedList/mSnapshot
//加载新数据的逻辑
mUpdateCallback.onInserted(0, pagedList.size());

onCurrentListChanged(null, pagedList, commitCallback);
return;
}

//旧数据和新数据都不为null,就要做差分处理了
if (mPagedList != null) {
// first update scheduled on this list, so capture mPages as a snapshot, removing
// callbacks so we don't have resolve updates against a moving target
mPagedList.removeWeakCallback(mPagedListCallback);
mSnapshot = (PagedList<T>) mPagedList.snapshot();
mPagedList = null;
}

if (mSnapshot == null || mPagedList != null) {
throw new IllegalStateException("must be in snapshot state to diff");
}

//做差分是先获取snapshot,再异步处理的,处理完最后在更新到UI上
final PagedList<T> oldSnapshot = mSnapshot;
final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
//后台线程中处理
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result;
//做差分算法的核心逻辑
result = PagedStorageDiffHelper.computeDiff(
oldSnapshot.mStorage,
newSnapshot.mStorage,
mConfig.getDiffCallback());
//处理完成后回到主线程
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
//先对比一下generation,在多线程的场景下,假如有多次执行,那只要对比Generation是否一致就可以丢弃前几次的结果
if (mMaxScheduledGeneration == runGeneration) {
//将结果反应到UI上
latchPagedList(pagedList, newSnapshot, result,
oldSnapshot.mLastLoad, commitCallback);
}
}
});
}
});
}

看下差分是怎么做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
static <T> DiffUtil.DiffResult computeDiff(
final PagedStorage<T> oldList,
final PagedStorage<T> newList,
final DiffUtil.ItemCallback<T> diffCallback) {
final int oldOffset = oldList.computeLeadingNulls();
final int newOffset = newList.computeLeadingNulls();

final int oldSize = oldList.size() - oldOffset - oldList.computeTrailingNulls();
final int newSize = newList.size() - newOffset - newList.computeTrailingNulls();

//这是原生提供好的差分计算方法
return DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition + oldOffset);
T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
if (oldItem == null || newItem == null) {
return null;
}
return diffCallback.getChangePayload(oldItem, newItem);
}

@Override
public int getOldListSize() {
return oldSize;
}

@Override
public int getNewListSize() {
return newSize;
}

@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition + oldOffset);
T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
if (oldItem == newItem) {
return true;
}
//noinspection SimplifiableIfStatement
if (oldItem == null || newItem == null) {
return false;
}
//这里就调了我们一开始创建的时候传进来的DiffUtil.ItemCallback
return diffCallback.areItemsTheSame(oldItem, newItem);
}

@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition + oldOffset);
T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
if (oldItem == newItem) {
return true;
}
//noinspection SimplifiableIfStatement
if (oldItem == null || newItem == null) {
return false;
}

return diffCallback.areContentsTheSame(oldItem, newItem);
}
}, true);
}

再看看差分的结果是怎么反应到UI上的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void latchPagedList(
@NonNull PagedList<T> newList,
@NonNull PagedList<T> diffSnapshot,
@NonNull DiffUtil.DiffResult diffResult,
int lastAccessIndex,
@Nullable Runnable commitCallback) {
if (mSnapshot == null || mPagedList != null) {
throw new IllegalStateException("must be in snapshot state to apply diff");
}

PagedList<T> previousSnapshot = mSnapshot;
mPagedList = newList;
mSnapshot = null;

// dispatch update callback after updating mPagedList/mSnapshot
//这里是具体更新UI的方法
PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
previousSnapshot.mStorage, newList.mStorage, diffResult);

newList.addWeakCallback(diffSnapshot, mPagedListCallback);

if (!mPagedList.isEmpty()) {
// Transform the last loadAround() index from the old list to the new list by passing it
// through the DiffResult. This ensures the lastKey of a positional PagedList is carried
// to new list even if no in-viewport item changes (AsyncPagedListDiffer#get not called)
// Note: we don't take into account loads between new list snapshot and new list, but
// this is only a problem in rare cases when placeholders are disabled, and a load
// starts (for some reason) and finishes before diff completes.
int newPosition = PagedStorageDiffHelper.transformAnchorIndex(
diffResult, previousSnapshot.mStorage, diffSnapshot.mStorage, lastAccessIndex);

// Trigger load in new list at this position, clamped to list bounds.
// This is a load, not just an update of last load position, since the new list may be
// incomplete. If new list is subset of old list, but doesn't fill the viewport, this
// will likely trigger a load of new data.
//可能回去加载下一页
mPagedList.loadAround(Math.max(0, Math.min(mPagedList.size() - 1, newPosition)));
}

onCurrentListChanged(previousSnapshot, mPagedList, commitCallback);
}

具体的执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
static <T> void dispatchDiff(ListUpdateCallback callback,
final PagedStorage<T> oldList,
final PagedStorage<T> newList,
final DiffUtil.DiffResult diffResult) {

final int trailingOld = oldList.computeTrailingNulls();
final int trailingNew = newList.computeTrailingNulls();
final int leadingOld = oldList.computeLeadingNulls();
final int leadingNew = newList.computeLeadingNulls();

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.onXXX
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);
}

// apply the diff, with an offset if needed
if (leadingNew != 0) {
//这其实就是原生提供的差分结果应用,平时开发很少用的
diffResult.dispatchUpdatesTo(new OffsettingListUpdateCallback(leadingNew, callback));
} else {
diffResult.dispatchUpdatesTo(callback);
}
}

可以看到整个流程中多次出现了 callback.onInserted() 或者 callback.onRemoved() 系列方法,他们的具体实现是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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);
}
}

这里就是我们很熟悉的 notifyXXX() 系列方法了,也就是说不论是全部加载新数据也好,全部删除旧数据也好,或者差分计算后应用新结果,最后都是通过自己调用不通过的 notifyXXX() 系列方法来实现UI更新的。

总结

那么结合前面LiveData实例的创建,不难看出整个流程:

  1. 创建一个 ComputableLiveData 对象,在订阅时进行计算,调用 compute() 方法获取最新的数据并通知给外部调用者
  2. compute() 方法内,创建一个 LimitOffsetDataSouce 的实例和一个 PagedList 的实例,让后者持有前者,并给DataSouce添加了一个刷新回调
  3. 在数据库刷新时,会通知 LimitOffsetDataSouceinvalidate() ,此时会触发第二步中添加的刷新回调,在这个回调内又会去触发 ComputableLiveData 的刷新,再进行一次计算
  4. 每次 ComputableLiveData 重新计算后都会根据当前DataSouce类型去创建一个 PagedList 对象
  5. 在有新的数据到来后,会调 submit() 方法来更新 PagedList

此外,还有一些从源码中得出的结论:

  1. 因为数据刷新的时候会重新创建 DataSoucePagedList ,所以从外部持有一个对他们的引用是没有意义的。如果需要保存一些生命周期更长久的变量,不要放在这里面。
  2. 在需要刷新全部数据时,可以直接清空数据库,这样会触发 LimitOffsetDataSource 中的刷新回调,继而通知 ComputableLiveData 重新计算。
  3. 在调用 observe() 注册监听后,才会计算,才会创建第一个 DataSouePagedList
  4. 在需要给 AdapterHeader 或者 Footer 时,可以重写 DataObserver ,通过修改具体的每个 notifyXXX() 方法的索引参数来规避一些更新UI的问题