この記事を読む前に、同シリーズの Android Jetpack コンポーネントに関する記事を以下のように読むことをお勧めします:
- Android Jetpack コンポーネントの Lifecycle 編
- Android Jetpack コンポーネントの LiveData 編
- Android Jetpack コンポーネントの ViewModel 編
- Android Jetpack コンポーネントの DataBinding 編
- Android Jetpack コンポーネントの BindingAdapter 編
- Android Jetpack コンポーネントの使用可能な観察データオブジェクト
この記事では Paging Library ライブラリの使用について説明します。そのソースコードの解析は次の記事で紹介します。Paging Library コンポーネントは Android Jetpack の一部であり、Google が提供する公式のページネーションコンポーネントです。プロジェクトで Google が新たに提供する公式アーキテクチャコンポーネント(LiveData、Lifecycle、ViewModel など)を使用している場合、このページネーションコンポーネントをプロジェクトに導入することを検討できます。その利点は、データをシームレスに読み込むことができ、ユーザー体験をある程度向上させることです。
Paging コンポーネントを使用してデータをページごとに読み込むプロセスを簡単に説明します。DataSource はネットワークまたはデータベースからデータを読み込み、データを PagedList に保存します。submitList を使用してデータを PagedListAdapter に提出し、データが変更されるとバックグラウンドスレッドでデータの差分が計算され、最終的に PagedListAdapter が RecyclerView にデータの更新を通知します。
- データの準備
- Paging Library コンポーネントの導入
- DataSource のカスタマイズ
- ページネーションパラメータの設定
- データの読み込みと表示
- 効果のテスト
- Paging Library ソースコードの解析
データの準備#
ここでは、干貨集中营のオープン API を使用してテストを行います。具体的には以下の通りです:
public interface CommonAPI {
// ここでは干貨集中营オープンAPIを使用します:http://gank.io/api/search/query/listview/category/Android/count/10/page/1
@GET("api/search/query/listview/category/Android/count/8/page/{page}")
Call<List<DataBean.ResultsBean>> getArticleList1(@Path("page") int page);
}
Paging Library コンポーネントの導入#
Paging Library ライブラリを以下のように導入します:
def paging_version = "2.1.0"
// androidx
implementation "androidx.paging:paging-runtime:$paging_version"
// 古いバージョン (page library 2.0.0-rc01)
implementation "android.arch.paging:runtime:$paging_version"
ここで使用しているのは androidx の最新バージョンです。
DataSource のカスタマイズ#
カスタム DataSource を使用してデータを読み込みます。ここではネットワークデータを読み込むために PageKeyedDataSource を使用するのが適切であり、PageKeyedDataSource を継承してカスタム DataSource を以下のように作成します:
// カスタムDataSource
public class MDataSource extends PageKeyedDataSource<String, DataBean.ResultsBean> {
private static final String TAG = MDataSource.class.getSimpleName();
private int mPage = 1;
public MDataSource() {
}
// 初期化
@Override
public void loadInitial(@NonNull LoadInitialParams<String> params,
@NonNull final LoadInitialCallback<String, DataBean.ResultsBean> callback) {
Log.i(TAG, "--loadInitial-->");
CommonAPI api = RetrofitApi.getInstance().mRetrofit.create(CommonAPI.class);
Call<List<DataBean.ResultsBean>> call = api.getArticleList1(mPage);
call.enqueue(new Callback<List<DataBean.ResultsBean>>() {
@Override
public void onResponse(Call<List<DataBean.ResultsBean>> call, Response<List<DataBean.ResultsBean>> response) {
Log.i(TAG, "--onResponse-->" + response.toString());
if (response.isSuccessful() && response.code() == 200) {
List<DataBean.ResultsBean> data = response.body();
callback.onResult(data, "before", "after");
}
}
@Override
public void onFailure(Call<List<DataBean.ResultsBean>> call, Throwable t) {
Log.i(TAG, "--onFailure-->" + t.getMessage());
}
});
}
// 前のページを読み込む
@Override
public void loadBefore(@NonNull LoadParams<String> params,
@NonNull LoadCallback<String, DataBean.ResultsBean> callback) {
Log.i(TAG, "--loadBefore-->" + params.key);
}
// 次のページを読み込む
@Override
public void loadAfter(@NonNull final LoadParams<String> params,
@NonNull final LoadCallback<String, DataBean.ResultsBean> callback) {
Log.i(TAG, "--loadAfter-->" + params.key);
mPage++;
CommonAPI api = RetrofitApi.getInstance().mRetrofit.create(CommonAPI.class);
Call<List<DataBean.ResultsBean>> call = api.getArticleList1(mPage);
call.enqueue(new Callback<List<DataBean.ResultsBean>>() {
@Override
public void onResponse(Call<List<DataBean.ResultsBean>> call, Response<List<DataBean.ResultsBean>> response) {
Log.i(TAG, "--onResponse-->" + response.toString());
if (response.isSuccessful() && response.code() == 200) {
List<DataBean.ResultsBean> data = response.body();
callback.onResult(data, params.key);
}
}
@Override
public void onFailure(Call<List<DataBean.ResultsBean>> call, Throwable t) {
Log.i(TAG, "--onFailure-->" + t.getMessage());
}
});
}
}
非常にシンプルで余分なものはありません。詳細については後のソースコード解析を参照してください。データの変化に応じて新しい DataSource を作成するためのファクトリを作成します:
// MDataSource作成ファクトリ
public class MDataSourceFactory extends DataSource.Factory<String, DataBean.ResultsBean> {
public MDataSourceFactory() {
}
@NonNull
@Override
public DataSource<String, DataBean.ResultsBean> create() {
MDataSource mDataSource = new MDataSource();
return mDataSource;
}
}
ページネーションパラメータの設定#
ViewModel 内で PagedList.Config を作成し、ページネーションパラメータを設定します。DataSource ファクトリオブジェクトを作成し、最終的にページネーションをサポートする LiveData データを生成します。具体的には以下の通りです:
// ViewModel
public class MViewModel extends ViewModel {
private int pageSize = 20;
// PagedList設定
private PagedList.Config config = new PagedList.Config.Builder()
.setInitialLoadSizeHint(pageSize)//最初の読み込み数を設定
.setPageSize(pageSize)//各ページの読み込み数を設定
.setPrefetchDistance(2)//各ページの最後のデータ項目から次のページデータを事前に読み込む距離を設定
.setEnablePlaceholders(false)//UIプレースホルダーを有効にするかどうかを設定
.build();
// DataSource.Factory
private DataSource.Factory<String,DataBean.ResultsBean> factory = new MDataSourceFactory();
// LiveData
private LiveData<PagedList<DataBean.ResultsBean>> mPagedList = new LivePagedListBuilder<>(factory, config)
.build();
public LiveData<PagedList<DataBean.ResultsBean>> getPagedList() {
return mPagedList;
}
}
データの読み込みと表示#
ここでは LiveData を使用して読み込まれたデータを監視し、submitList を使用してデータを PagedListAdapter に提出します。新旧データの差異をバックグラウンドスレッドで比較し、最終的に RecyclerView を更新します。具体的には以下の通りです:
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private RecyclerView mRecyclerView;
private ArticleAdapter mAdapter;
private MViewModel mViewModel;
private static DiffUtil.ItemCallback<DataBean.ResultsBean> itemCallback = new DiffUtil.ItemCallback<DataBean.ResultsBean>() {
@Override
public boolean areItemsTheSame(@NonNull DataBean.ResultsBean oldItem, @NonNull DataBean.ResultsBean newItem) {
return oldItem.getGanhuo_id() == newItem.getGanhuo_id();
}
@Override
public boolean areContentsTheSame(@NonNull DataBean.ResultsBean oldItem, @NonNull DataBean.ResultsBean newItem) {
return oldItem.equals(newItem);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.rvData);
mAdapter = new ArticleAdapter(itemCallback);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter);
ViewModelProvider mViewModelProvider = new ViewModelProvider(this,
new ViewModelProvider.AndroidViewModelFactory(getApplication()));
mViewModel = mViewModelProvider.get(MViewModel.class);
}
public void getData(View view) {
mViewModel.getPagedList().observe(this, new Observer<PagedList<DataBean.ResultsBean>>() {
@Override
public void onChanged(PagedList<DataBean.ResultsBean> dataBeans) {
Log.i(TAG, "--onChanged-->");
mAdapter.submitList(dataBeans);
}
});
}
}
効果のテスト#
以上が Page Library ライブラリの使用方法です。
Paging Library ソースコードの解析#
LiveData の observer メソッドに入ると、ソースコードは以下の通りです:
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// 無視
return;
}
// このLifecycleBoundObserverを記憶しておく、後で使う
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("異なるライフサイクルで同じオブザーバーを追加することはできません");
}
if (existing != null) {
return;
}
// Observerを追加、具体的にはLifecycleBoundObserver
owner.getLifecycle().addObserver(wrapper);
}
LifecycleRegistry の addObserver メソッドを続けて見てみると、ソースコードは以下の通りです:
@Override
public void addObserver(@NonNull LifecycleObserver observer) {
// ...
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
// ObserverWithStateのdispatchEventメソッド
statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / sublingが変更された可能性があるため再計算
targetState = calculateTargetState(observer);
}
// ...
}
static class ObserverWithState {
State mState;
LifecycleEventObserver mLifecycleObserver;
ObserverWithState(LifecycleObserver observer, State initialState) {
mLifecycleObserver = Lifecycling.lifecycleEventObserver(observer);
mState = initialState;
}
void dispatchEvent(LifecycleOwner owner, Event event) {
State newState = getStateAfter(event);
mState = min(mState, newState);
// 実際に呼び出されるのはLifecycleBoundObserverのonStateChangedメソッド
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}
}
LiveData の内部クラス LifecycleBoundObserver の onStateChanged メソッドを続けて見てみると、ソースコードは以下の通りです:
// メソッド
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
// Lifecycleコンポーネントが監視するライフサイクルメソッドのコールバック
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
// ソースコードshouldBeActiveメソッドを見ればわかるように、Activityの状態がON_START以降であればtrueを返す
activeStateChanged(shouldBeActive());
}
// activeStateChangedメソッド
void activeStateChanged(boolean newActive) {
// mActiveはデフォルトでfalseなので、成立しない
// もし二回目の実行でmActiveがfalseであれば、処理を続行せず、最終的な結果はonChangedメソッドに戻らない
if (newActive == mActive) {
return;
}
// 最初の実行後、mActiveの値はtrueに設定される
mActive = newActive;
// 活動中のオブザーバーがあれば、つまりコンポーネントの状態がSTARTまたはRESUMEであれば、mActiveCountは0ではない
boolean wasInactive = LiveData.this.mActiveCount == 0;
// mActiveCountが0より大きい
LiveData.this.mActiveCount += mActive ? 1 : -1;
// wasInactiveとmActiveが両方trueであれば、実行
if (wasInactive && mActive) {
onActive();
}
// mActiveCountが0で、false && falseであれば、実行しない
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
// mActiveがtrueであれば
if (mActive) {
// ObserverWrapperを分配処理、つまり追加されたオブザーバーLifecycleBoundObserver
dispatchingValue(this);
}
}
ここまで来ると、少なくとも PagedList が初期データを読み込むときに onChanged メソッドがコールバックされる理由が説明できますが、次のページデータを読み込むときには onChanged メソッドが再度コールバックされない理由もわかります。
次に LiveData の dispatchingValue メソッドを見てみると、ソースコードは以下の通りです:
// dispatchingValueメソッド
void dispatchingValue(@Nullable ObserverWrapper initiator) {
// ...
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
// オブザーバーを取得して処理
considerNotify(iterator.next().getValue());
//...
}
// ...
}
// considerNotifyメソッド
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// オブザーバーが活動中でない場合、オブザーバーに通知しない
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
// オブザーバーに通知
observer.mObserver.onChanged((T) mData);
}
これで、LiveData<PagedList> の作成から更新までの分析が完了しました。
次に PagedList がどのように生成されるかを見ていきます。PagedList 作成の重要なソースコードは以下の通りです:
// PagedListの作成
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
// buildメソッドを続けて見てみると、PagedListのcreateメソッドが呼び出されます
public PagedList<Value> build() {
// ...
return PagedList.create(
mDataSource,
mNotifyExecutor,
mFetchExecutor,
mBoundaryCallback,
mConfig,
mInitialKey);
}
// PagedListを実際に作成する
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) {
// PageKeyedDataSourceはContiguousDataSourceを継承しており、dataSource.isContiguous()はtrue
if (dataSource.isContiguous() || !config.enablePlaceholders) {
int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
// PageKeyedDataSourceを使用しない
if (!dataSource.isContiguous()) {
//noinspection unchecked
dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
.wrapAsContiguousWithoutPlaceholders();
if (key != null) {
lastLoad = (Integer) key;
}
}
// ContiguousPagedListを作成して返す、つまりPagedList
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 の作成を続けて見てみると、重要なソースコードは以下の通りです:
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.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
// ...
}
ここで抽象クラス ContiguousDataSource の dispatchLoadInitial メソッドが呼び出されます。具体的な実装クラス PageKeyedDataSource の dispatchLoadInitial メソッドを見てみると、ソースコードは以下の通りです:
@Override
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
// LoadInitialCallbackを作成します。これはカスタムDataSourceがネットワークからデータを読み込むときに使用するcallbackです
LoadInitialCallbackImpl<Key, Value> callback =
new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
// カスタムDataSourceはloadInitialメソッドを実装する必要があり、ここでコールバックを完了します
loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
// コールバックの実行を主スレッドに設定します
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}
ここまで来ると、コールバックされたデータがどのように保存されるのかはまだわかりません。callback の具体的な実装とコールバックを見てみると、ソースコードは以下の通りです:
static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {
final LoadCallbackHelper<Value> mCallbackHelper;
private final PageKeyedDataSource<Key, Value> mDataSource;
private final boolean mCountingEnabled;
// LoadInitialCallbackImplのコンストラクタ
LoadInitialCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
boolean countingEnabled, @NonNull PageResult.Receiver<Value> receiver) {
// ここでResultTypeがPageResult.INITであることを記憶しておく必要があります。これは後でPageResult.INITに遭遇します
mCallbackHelper = new LoadCallbackHelper<>(
dataSource, PageResult.INIT, null, receiver);
mDataSource = dataSource;
mCountingEnabled = countingEnabled;
}
// このonResultコールバックは明らかにPageKeyedDataSourceがデータを設定するために使用するものではなく、PositionalDataSourceが使用するもので、ここは省略します
@Override
public void onResult(@NonNull List<Value> data, int position, int totalCount,
@Nullable Key previousPageKey, @Nullable Key nextPageKey) {
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
// データを分配する前にキーを設定します。これにより、準備が整います
mDataSource.initKeys(previousPageKey, nextPageKey);
int trailingUnloadedCount = totalCount - position - data.size();
if (mCountingEnabled) {
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
data, position, trailingUnloadedCount, 0));
} else {
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
}
}
}
// このonResultコールバックはPageKeyedDataSourceがデータを設定するために使用します
@Override
public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
@Nullable Key nextPageKey) {
// DataSourceが有効な場合、dispatchInvalidResultIfInvalidはfalseを返します
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
// loadInitialで設定されたpreviousPageKey、nextPageKeyを初期化します。これは前のページを読み込むか次のページを読み込むかを区別するために使用されます
mDataSource.initKeys(previousPageKey, nextPageKey);
// コールバックされたデータdataを分配します
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
}
}
}
ここでネットワークから読み込まれたデータを取得しました。dispatchResultToReceiver メソッドを続けて見てみると、データがどのように処理されるかがわかります。ソースコードは以下の通りです:
void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
Executor executor;
synchronized (mSignalLock) {
if (mHasSignalled) {
throw new IllegalStateException(
"callback.onResultはすでに呼び出されており、再度呼び出すことはできません。");
}
mHasSignalled = true;
executor = mPostExecutor;
}
// executorがnullの場合、上記の分析でこのコールバックexecutorが主スレッドで実行されることがわかります
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
// データをコールバックします
mReceiver.onPageResult(mResultType, result);
}
});
} else { // 非主スレッドで直接コールバック
mReceiver.onPageResult(mResultType, result);
}
}
ここで onPageResult の具体的な実装を見てみると、このプロセスは ContiguousPagedList で実装されていることがわかります。ソースコードは以下の通りです:
PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
@AnyThread
@Override
public void onPageResult(@PageResult.ResultType int resultType,
@NonNull PageResult<V> pageResult) {
// ...
List<V> page = pageResult.page;
// 前面で初期データを読み込む際、データの状態はPageResult.INITです
// つまり、初期呼び出し時のloadInitialです
if (resultType == PageResult.INIT) {
// 1.データをPagedStorageに保存
// 2.最終的にRecyclerView.AdapterのnotifyItemRangeInserted(position, count)メソッドを呼び出します。positionは0です
mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
pageResult.positionOffset, ContiguousPagedList.this);
// ...
} else {
// ...
// LoadCallbackHelperを作成する際、データの状態はPageResult.APPENDです
// つまりloadAfterを呼び出すときです
if (resultType == PageResult.APPEND) {
if (skipNewPage && !trimFromFront) {
// このデータを追加しない、ドロップします
mAppendItemsRequested = 0;
mAppendWorkerState = READY_TO_FETCH;
} else {
// 1.データをPagedStorageに保存
// 2.最終的にloadAfterをコールバックします
mStorage.appendPage(page, ContiguousPagedList.this);
}
// LoadCallbackHelperを作成する際、データの状態はPageResult.PREPENDです
// つまりloadBeforeを呼び出すときです
} else if (resultType == PageResult.PREPEND) {
if (skipNewPage && trimFromFront) {
// このデータを追加しない、ドロップします
mPrependItemsRequested = 0;
mPrependWorkerState = READY_TO_FETCH;
} else {
// 1.データをPagedStorageに保存
// 2.最終的にloadBeforeをコールバックします
mStorage.prependPage(page, ContiguousPagedList.this);
}
} else {
throw new IllegalArgumentException("予期しないresultType " + resultType);
}
// ...
}
};
これで PagedList の作成と保存プロセスが終了しました。以前、observer からの PagedList のデータコールバックが始まり、onChanged メソッドが呼び出され、RecyclerView の Adapter にデータが設定されるコードは以下の通りです:
mViewModel.getPagedList().observe(this, new Observer<PagedList<DataBean.ResultsBean>>() {
@Override
public void onChanged(PagedList<DataBean.ResultsBean> dataBeans) {
// 表示するデータを設定します。初期化時にのみコールバックされます
// すでに表示リストが存在する場合、バックグラウンドスレッドでデータの差異が計算され、データの更新が通知されます
mAdapter.submitList(dataBeans);
}
});
上記で述べたように、すでに表示リストが存在する場合、バックグラウンドスレッドでデータの差異が計算され、データの更新が通知されます。このバックグラウンドスレッドはどこにあるのでしょうか。mAdapter の submitList メソッドは最終的に以下の submitList メソッドを呼び出します。このメソッド内で AsyncDifferConfig を介してバックグラウンドスレッドでデータの差異を計算します。このバックグラウンドスレッドプールは指定しない場合、固定サイズは 2 のスレッドプールです。ソースコードは以下の通りです:
@SuppressWarnings("ReferenceEquality")
public void submitList(@Nullable final PagedList<T> pagedList,
@Nullable final Runnable commitCallback) {
// ...
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());
// 計算結果を主スレッドにコールバックし、データセットの更新を通知し、最終的にRecyclerViewにデータを更新します
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
latchPagedList(pagedList, newSnapshot, result,
oldSnapshot.mLastLoad, commitCallback);
}
}
});
}
});
}
latchPagedList メソッドを続けて見てみると、ここでデータの更新操作とデータの読み取りが行われます。ソースコードは以下の通りです:
void latchPagedList(
@NonNull PagedList<T> newList,
@NonNull PagedList<T> diffSnapshot,
@NonNull DiffUtil.DiffResult diffResult,
int lastAccessIndex,
@Nullable Runnable commitCallback) {
// ...
// mPageListを更新し、RecyclerViewにデータの更新を通知します
PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
previousSnapshot.mStorage, newList.mStorage, diffResult);
newList.addWeakCallback(diffSnapshot, mPagedListCallback);
if (!mPagedList.isEmpty()) {
// データを読み取ります
mPagedList.loadAround(Math.max(0, Math.min(mPagedList.size() - 1, newPosition)));
}
onCurrentListChanged(previousSnapshot, mPagedList, commitCallback);
}
mWrapped は ListUpdateCallback であり、AdapterListUpdateCallback はこのインターフェースを実装しており、RecyclerView の Adapter にデータの更新を通知します。
latchPagedList メソッド内で PagedList の loadAround メソッドを呼び出してデータを取得します。これは PageKeyedDataSource を使用する際に実装された loadBefore および loadAfter メソッドを使用します。ここではソースコードを省略します。
Page Library のページネーション読み込みプロセスの分析はこれで完了しました。ソースコードを見れば、onChanged メソッドは初期化時にのみコールバックされますが、次のページデータを継続的に読み込む理由がわかります。