banner
jzman

jzman

Coding、思考、自觉。
github

Android JetpackコンポーネントのPaging Libraryについて

この記事を読む前に、同シリーズの Android Jetpack コンポーネントに関する記事を以下のように読むことをお勧めします:

この記事では Paging Library ライブラリの使用について説明します。そのソースコードの解析は次の記事で紹介します。Paging Library コンポーネントは Android Jetpack の一部であり、Google が提供する公式のページネーションコンポーネントです。プロジェクトで Google が新たに提供する公式アーキテクチャコンポーネント(LiveData、Lifecycle、ViewModel など)を使用している場合、このページネーションコンポーネントをプロジェクトに導入することを検討できます。その利点は、データをシームレスに読み込むことができ、ユーザー体験をある程度向上させることです。

Paging コンポーネントを使用してデータをページごとに読み込むプロセスを簡単に説明します。DataSource はネットワークまたはデータベースからデータを読み込み、データを PagedList に保存します。submitList を使用してデータを PagedListAdapter に提出し、データが変更されるとバックグラウンドスレッドでデータの差分が計算され、最終的に PagedListAdapter が RecyclerView にデータの更新を通知します。

  1. データの準備
  2. Paging Library コンポーネントの導入
  3. DataSource のカスタマイズ
  4. ページネーションパラメータの設定
  5. データの読み込みと表示
  6. 効果のテスト
  7. 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);
            }
        });
    }
}

効果のテスト#

image

以上が 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 メソッドは初期化時にのみコールバックされますが、次のページデータを継続的に読み込む理由がわかります。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。