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 about 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 is 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 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. Here, loading network data is more suitable with PageKeyedDataSource. 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 content. For details, please 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#

Create PagedList.Config in ViewModel 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, as shown in the source code below:

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    // Remember this LifecycleBoundObserver, it will be useful later
    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);
}

Next, we check the addObserver method of LifecycleRegistry, as shown in the source code below:

@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, we check the onStateChanged method of the internal class LifecycleBoundObserver in LiveData, as shown in the source code below:

// 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;
    }
    // As seen in the shouldBeActive method in the source code, it returns true as long as the Activity state is after ON_START
    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 the final outcome of not returning to the onChanged method
    if (newActive == mActive) {
        return;
    }
    // After the first execution, the value of 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
    LiveData.this.mActiveCount += mActive ? 1 : -1;
    // Both wasInactive and mActive are true, execute
    if (wasInactive && mActive) {
        onActive();
    }
    // mActiveCount greater than 0, false && false, do not execute
    if (LiveData.this.mActiveCount == 0 && !mActive) {
        onInactive();
    }
    // mActive is true
    if (mActive) {
        // Dispatch processing for ObserverWrapper, which is the added observer 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, we check the dispatchingValue method of LiveData, as shown in the source code below:

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

Next, we check the key method build, as shown in the source code below:

// 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, still use the previous data, when DataSource is valid, execute once to return PagedList
            return mList;
        }
    }.getLiveData();
}

So far, the creation of LiveData<PagedList> has been analyzed from creation to update.

Next, we will 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 check 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 from 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);
    }
}

Next, we check the creation of ContiguousPagedList, with the key source code 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. We will check the specific implementation of this method in the PageKeyedDataSource class, as shown in the source code below:

@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 loadInitial method that the custom DataSource needs to implement, the callback is completed 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 stored, so we continue to check the specific implementation and callback of the callback, as shown in the source code below:

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 should be noted that the 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 we will skip
    @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 or next page
            mDataSource.initKeys(previousPageKey, nextPageKey);
            // Dispatch the data returned
            mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
        }
    }
}

Here we have obtained the data loaded from the network. Next, we check how the dispatchResultToReceiver method processes the data, as shown in the source code below:

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 runs 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 directly callback
        mReceiver.onPageResult(mResultType, result);
    }
}

Next, we check the specific implementation of onPageResult. From the source code, we know that this process is implemented in ContiguousPagedList, as shown below:

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 data loading, 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 storage process of PagedList has been completed. Previously, we analyzed that the data callback from the observer would call the onChanged method, where the data to be displayed is set for the RecyclerView's Adapter, as shown in the code below:

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 callback once during initialization
        // If there is already a displayed list, it will calculate the data differences in the background thread and notify data updates
        mAdapter.submitList(dataBeans);
    }
});

As mentioned, if there is already a displayed list, it will calculate the data differences 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, where the background thread for calculating data differences is obtained through AsyncDifferConfig. This background thread pool, if not specified, is a fixed size of 2, as shown in the source code below:

@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 to execute the calculation of data differences and callback the calculation 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 update, thus notifying RecyclerView to update data
            mMainThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    if (mMaxScheduledGeneration == runGeneration) {
                        latchPagedList(pagedList, newSnapshot, result,
                                oldSnapshot.mLastLoad, commitCallback);
                    }
                }
            });
        }
    });
}

Next, we check the latchPagedList source code, where data updates and reading operations will be performed, as shown below:

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, as shown in the source code below:

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 update the data.

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

The pagination loading process of the Paging 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.