banner
jzman

jzman

Coding、思考、自觉。
github

Android Jetpack Components: Paging Library

Before reading this article, you may want to read the following articles in the same series on Android Jetpack components:

This article will introduce the use of the Paging Library, with source code analysis to be covered in the next article. The Paging Library component is part of Android Jetpack and is Google's official pagination component. If your project uses Google's newly released official architecture components, such as LiveData, Lifecycle, ViewModel, etc., you can try to integrate this pagination component into your project. Its advantage is seamless loading of more data, which improves user experience to some extent.

Here’s a brief overview of the process of using the paging component to load data in pages: DataSource is responsible for loading data from the network or database and storing it in PagedList. The data is submitted to PagedListAdapter using submitList. When the data changes, the differences are calculated in the background thread, and finally, PagedListAdapter notifies RecyclerView to update the data.

  1. Prepare data
  2. Introduce Paging Library component
  3. Customize DataSource
  4. Configure pagination parameters
  5. Load and display data
  6. Test the effect
  7. Source code analysis of Paging Library

Prepare Data#

Here, we will use the open-source API from Gank.io for testing, as follows:

public interface CommonAPI {
    // Using Gank.io open-source 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);
}

Introduce Paging Library Component#

Introduce the Paging Library as follows:

def paging_version = "2.1.0"
// androidx
implementation "androidx.paging:paging-runtime:$paging_version"
// Old version (page library 2.0.0-rc01)
implementation "android.arch.paging:runtime:$paging_version"

Here, we are using the latest version of androidx.

Customize DataSource#

Customize DataSource to load data. Using PageKeyedDataSource is more appropriate for loading network data. Inherit PageKeyedDataSource to customize DataSource as follows:

// Custom DataSource
public class MDataSource extends PageKeyedDataSource<String, DataBean.ResultsBean> {
    private static final String TAG = MDataSource.class.getSimpleName();

    private int mPage = 1;

    public MDataSource() {
    }

    // Initialize
    @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());
            }

        });
    }

    // Load previous page
    @Override
    public void loadBefore(@NonNull LoadParams<String> params,
                           @NonNull LoadCallback<String, DataBean.ResultsBean> callback) {
        Log.i(TAG, "--loadBefore-->" + params.key);
    }

    // Load next page
    @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());
            }

        });
    }
}

It's simple with no extra elements. For details, refer to the source code analysis later in the article. Create a factory to facilitate the creation of a new DataSource when data changes as follows:

// MDataSource creation factory
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;
    }
}

Configure Pagination Parameters#

In ViewModel, create PagedList.Config and configure pagination parameters. Create a DataSource factory object, and finally generate LiveData data that supports pagination, as follows:

// ViewModel
public class MViewModel extends ViewModel {

    private int pageSize = 20;

    // PagedList configuration
    private PagedList.Config config = new PagedList.Config.Builder()
            .setInitialLoadSizeHint(pageSize) // Set the number of items to load initially
            .setPageSize(pageSize) // Set the number of items to load per page
            .setPrefetchDistance(2) // Set the distance from the last item to prefetch the next page
            .setEnablePlaceholders(false) // Set whether to enable UI placeholders
            .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;
    }
}

Load and Display Data#

Here, we use LiveData to observe the loaded data, and then use submitList to submit the data to PagedListAdapter. It will compare the differences between the new and old data in the background thread and finally update the RecyclerView, as follows:

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

Test the Effect#

image

This is the usage of the Paging Library.

Source Code Analysis of Paging Library#

Next, we will start with the observer method of LiveData. The source code is as follows:

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    // Remember this LifecycleBoundObserver for later use
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    // Add Observer, specifically LifecycleBoundObserver
    owner.getLifecycle().addObserver(wrapper);
}

Continue to look at the addObserver method of LifecycleRegistry, the source code is as follows:

@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);
        // The dispatchEvent method of ObserverWithState
        statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
        popParentState();
        // mState / sibling may have been changed recalculate
        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);
        // The actual call is to the onStateChanged method of LifecycleBoundObserver
        mLifecycleObserver.onStateChanged(owner, event);
        mState = newState;
    }
}

Next, look at the onStateChanged method of the internal class LifecycleBoundObserver in LiveData, the source code is as follows:

// Method
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
    // Lifecycle component listening to lifecycle method callback
    if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
        removeObserver(mObserver);
        return;
    }
    // According to the source code shouldBeActive method, as long as the Activity's state is ON_START or later, it returns true
    activeStateChanged(shouldBeActive());
}

// activeStateChanged method
void activeStateChanged(boolean newActive) {
    // mActive defaults to false, so it does not hold
    // If executed a second time with mActive as false, it will not continue processing, resulting in not returning to the onChanged method
    if (newActive == mActive) {
        return;
    }
    // After the first execution, mActive will be set to true
    mActive = newActive;
    // As long as there are active observers, meaning the component's state is START or RESUME, mActiveCount will not equal 0, wasInactive will be true
    boolean wasInactive = LiveData.this.mActiveCount == 0;
    // mActiveCount is greater than 0
    LiveData.this.mActiveCount += mActive ? 1 : -1;
    // wasInactive and mActive are both true, execute
    if (wasInactive && mActive) {
        onActive();
    }
    // mActiveCount is greater than 0, false && false, do not execute
    if (LiveData.this.mActiveCount == 0 && !mActive) {
        onInactive();
    }
    // mActive is true
    if (mActive) {
        // Dispatch and process ObserverWrapper, which is the added LifecycleBoundObserver
        dispatchingValue(this);
    }
}

At this point, we can at least explain why the onChanged method is called only once during the initial data loading of PagedList, while it does not call onChanged when loading the next page of data.

Next, look at the dispatchingValue method of LiveData, the source code is as follows:

// dispatchingValue method
void dispatchingValue(@Nullable ObserverWrapper initiator) {
    // ...
    for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
        // Iterate to get Observer processing
        considerNotify(iterator.next().getValue());
        //...
    }
    // ...
}

// considerNotify method
private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }
    // If the observer is not in an active state, do not notify the observer
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    // Notify the observer
    observer.mObserver.onChanged((T) mData);
}

At this point, the result is returned to the specific observer.

Starting from the creation of mPagedList, the code for creating mPagedList is as follows:

// LiveData
private LiveData<PagedList<DataBean.ResultsBean>> mPagedList = new LivePagedListBuilder<>(factory,config)
        .build();

Continue to look at the key method build, the source code is as follows:

// build method
public LiveData<PagedList<Value>> build() {
    return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
            ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
}

// create method
private static <Key, Value> LiveData<PagedList<Value>> create(
      
    return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
        @Nullable
        private PagedList<Value> mList;
        @Nullable
        private DataSource<Key, Value> mDataSource;
        // DataSource invalidation callback
        private final DataSource.InvalidatedCallback mCallback =
                new DataSource.InvalidatedCallback() {
                    @Override
                    public void onInvalidated() {
                        // This callback usually indicates that a new data source is needed
                        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);
                // Create PagedList, how PagedList is created and stored will be discussed later
                mList = new PagedList.Builder<>(mDataSource, config)
                        .setNotifyExecutor(notifyExecutor)
                        .setFetchExecutor(fetchExecutor)
                        .setBoundaryCallback(boundaryCallback)
                        .setInitialKey(initializeKey)
                        .build();
            } while (mList.isDetached()); // When DataSource is invalid, use the previous data; when DataSource is valid, execute once and return PagedList
            return mList;
        }
    }.getLiveData();
}

At this point, LiveData<PagedList> has been analyzed from creation to update.

Next, continue to look at how PagedList is generated. The key source code for creating PagedList is as follows:

// Creation of PagedList
mList = new PagedList.Builder<>(mDataSource, config)
    .setNotifyExecutor(notifyExecutor)
    .setFetchExecutor(fetchExecutor)
    .setBoundaryCallback(boundaryCallback)
    .setInitialKey(initializeKey)
    .build();

// Continue to look at the build method, which calls the create method of PagedList
public PagedList<Value> build() {
    // ...
    return PagedList.create(
            mDataSource,
            mNotifyExecutor,
            mFetchExecutor,
            mBoundaryCallback,
            mConfig,
            mInitialKey);
}

// Truly creating 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 inherits ContiguousDataSource, dataSource.isContiguous() is true
    if (dataSource.isContiguous() || !config.enablePlaceholders) {
        int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
        // Using PageKeyedDataSource will not execute
        if (!dataSource.isContiguous()) {
            //noinspection unchecked
            dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
                    .wrapAsContiguousWithoutPlaceholders();
            if (key != null) {
                lastLoad = (Integer) key;
            }
        }
    
        // Create and return ContiguousPagedList, which is 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);
    }
}

Continue to look at the creation of ContiguousPagedList, the key source code is as follows:

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

Here, the dispatchLoadInitial method of the abstract class ContiguousDataSource is called. Look at the specific implementation of this method in PageKeyedDataSource, the source code is as follows:

@Override
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
        boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
        @NonNull PageResult.Receiver<Value> receiver) {
    // Create LoadInitialCallback, which is the callback used in the custom DataSource when loading data from the network
    LoadInitialCallbackImpl<Key, Value> callback =
            new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
    // The custom DataSource needs to implement the loadInitial method, which completes the callback here
    loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
    // Set the callback to execute on the main thread
    callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}

At this point, we still do not know how the data returned is saved, so continue to look at the specific implementation and callback of the callback, the source code is as follows:

static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {
    final LoadCallbackHelper<Value> mCallbackHelper;
    private final PageKeyedDataSource<Key, Value> mDataSource;
    private final boolean mCountingEnabled;
    // LoadInitialCallbackImpl constructor
    LoadInitialCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
            boolean countingEnabled, @NonNull PageResult.Receiver<Value> receiver) {
        // Here, it is necessary to remember that ResultType is PageResult.INIT, which is the data type when loading data initially, encountered PageResult.INIT later
        mCallbackHelper = new LoadCallbackHelper<>(
                dataSource, PageResult.INIT, null, receiver);
        mDataSource = dataSource;
        mCountingEnabled = countingEnabled;
    }

    // This onResult callback is obviously not used when setting data in PageKeyedDataSource, it should be used by PositionalDataSource, which will be skipped
    @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);

            // Setup keys before dispatching data, so guaranteed to be ready
            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));
            }
        }
    }

    // This onResult callback is used when setting data in PageKeyedDataSource
    @Override
    public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
            @Nullable Key nextPageKey) {
        // When DataSource is valid, dispatchInvalidResultIfInvalid returns false
        if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
            // Initialize previousPageKey and nextPageKey set in loadInitial, which should be used to distinguish loading the previous page or the next page
            mDataSource.initKeys(previousPageKey, nextPageKey);
            // Dispatch the returned data
            mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
        }
    }
}

Here we have obtained the data loaded from the network. Continue to look at the dispatchResultToReceiver method to see how the data is processed, the source code is as follows:

void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
    Executor executor;
    synchronized (mSignalLock) {
        if (mHasSignalled) {
            throw new IllegalStateException(
                    "callback.onResult already called, cannot call again.");
        }
        mHasSignalled = true;
        executor = mPostExecutor;
    }

    // If executor is null, because in the above analysis, the callback executor is assigned and run on the main thread
    if (executor != null) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                // Continue to callback the data
                mReceiver.onPageResult(mResultType, result);
            }
        });
    } else { // Non-main thread direct callback
        mReceiver.onPageResult(mResultType, result);
    }
}

Here, continue to look at the specific implementation of onPageResult. From the source code, we know that this process is implemented in ContiguousPagedList, the specific source code is as follows:

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;
        // Previously, during the initial loading of data, the data state was PageResult.INIT
        // That is, when calling loadInitial
        if (resultType == PageResult.INIT) {
            // 1. Save data to PagedStorage
            // 2. Finally call RecyclerView.Adapter's notifyItemRangeInserted(position, count) method, position is 0
            mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
                    pageResult.positionOffset, ContiguousPagedList.this);
            // ...
        } else {
            // ...
            // When creating LoadCallbackHelper, the data state is PageResult.APPEND
            // That is, when calling loadAfter
            if (resultType == PageResult.APPEND) {
                if (skipNewPage && !trimFromFront) {
                    // don't append this data, drop it
                    mAppendItemsRequested = 0;
                    mAppendWorkerState = READY_TO_FETCH;
                } else {
                    // 1. Save data to PagedStorage
                    // 2. Finally callback loadAfter
                    mStorage.appendPage(page, ContiguousPagedList.this);
                }
            // When creating LoadCallbackHelper, the data state is PageResult.PREPEND
            // That is, when calling loadBefore
            } else if (resultType == PageResult.PREPEND) {
                if (skipNewPage && trimFromFront) {
                    // don't append this data, drop it
                    mPrependItemsRequested = 0;
                    mPrependWorkerState = READY_TO_FETCH;
                } else {
                    // 1. Save data to PagedStorage
                    // 2. Finally callback loadBefore
                    mStorage.prependPage(page, ContiguousPagedList.this);
                }
            } else {
                throw new IllegalArgumentException("unexpected resultType " + resultType);
            }

            // ...
    }
};

At this point, the creation and saving process of PagedList has concluded. Previously, we analyzed that the data callback from the observer would call the onChanged method, in which the data to be displayed is set for the RecyclerView's Adapter, as follows:

mViewModel.getPagedList().observe(this, new Observer<PagedList<DataBean.ResultsBean>>() {
    @Override
    public void onChanged(PagedList<DataBean.ResultsBean> dataBeans) {
        // Set the data to be displayed, will only be called once during initialization
        // If there is already a displayed list, it will calculate the differences in data in the background thread and notify data updates
        mAdapter.submitList(dataBeans);
    }
});

As mentioned above, if there is already a displayed list, it will calculate the differences in data in the background thread and notify data updates. So where is this background thread? The submitList method of mAdapter ultimately calls the following submitList method, which uses AsyncDifferConfig to obtain the background thread for executing data difference calculations. If not specified, this background thread pool is a fixed size of 2, the source code is as follows:

@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();
    // Get the background thread pool for executing data difference calculations and callback the results to the main thread
    mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
        @Override
        public void run() {
        
            final DiffUtil.DiffResult result;
            result = PagedStorageDiffHelper.computeDiff(
                    oldSnapshot.mStorage,
                    newSnapshot.mStorage,
                    mConfig.getDiffCallback());

            // Callback the calculation results to the main thread, then notify the dataset updates, thus notifying RecyclerView to update data
            mMainThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    if (mMaxScheduledGeneration == runGeneration) {
                        latchPagedList(pagedList, newSnapshot, result,
                                oldSnapshot.mLastLoad, commitCallback);
                    }
                }
            });
        }
    });
}

Continue to look at the latchPagedList source code, here will perform data update operations and read data, the source code is as follows:

void latchPagedList(
        @NonNull PagedList<T> newList,
        @NonNull PagedList<T> diffSnapshot,
        @NonNull DiffUtil.DiffResult diffResult,
        int lastAccessIndex,
        @Nullable Runnable commitCallback) {
    // ...
    // Update mPageList and notify RecyclerView to update data
    PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
            previousSnapshot.mStorage, newList.mStorage, diffResult);

    newList.addWeakCallback(diffSnapshot, mPagedListCallback);

    if (!mPagedList.isEmpty()) {
        // Read data
        mPagedList.loadAround(Math.max(0, Math.min(mPagedList.size() - 1, newPosition)));
    }

    onCurrentListChanged(previousSnapshot, mPagedList, commitCallback);
}

The dispatchDiff method will ultimately call the dispatchLastEvent method, the source code is as follows:

public void dispatchLastEvent() {
    if (mLastEventType == TYPE_NONE) {
        return;
    }
    switch (mLastEventType) {
        case TYPE_ADD:
            mWrapped.onInserted(mLastEventPosition, mLastEventCount);
            break;
        case TYPE_REMOVE:
            mWrapped.onRemoved(mLastEventPosition, mLastEventCount);
            break;
        case TYPE_CHANGE:
            mWrapped.onChanged(mLastEventPosition, mLastEventCount, mLastEventPayload);
            break;
    }
    mLastEventPayload = null;
    mLastEventType = TYPE_NONE;
}

mWrapped is a ListUpdateCallback, and AdapterListUpdateCallback implements this interface and notifies the RecyclerView's Adapter to perform data updates.

In the latchPagedList method, the loadAround method of PagedList will be called to obtain data, which is implemented using the loadBefore and loadAfter methods in PageKeyedDataSource. The source code will not be pasted here.

The pagination loading process of the Page Library has been analyzed. From the source code, we can see that the onChanged method is only called once during initialization, but it can continuously load the next page of data.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.