当前位置: 首页 > 工具软件 > paging > 使用案例 >

Jetpack系列之Paging2.0 分页加载

淳于博文
2023-12-01

(一)概述

Paging是Jetpack中的一个数据分页加载组件,核心成员有 DataSource, PagedList, PagedListAdapter。

//  下拉刷新
api 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0'
api 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.0'
// paging分页(可选 2.1.1)
api 'androidx.paging:paging-runtime:3.1.1'

(二)源码解析

1)DataSource

Base class for loading pages of snapshot data into a PagedList.
DataSource is queried to load pages of content into a PagedList. A PagedList can grow as it loads more data, but the data loaded cannot be updated. If the underlying data set is modified, a new PagedList / DataSource pair must be created to represent the new data.
也就是说DataSuorce是一个提供数据来源,并将拿到的数据以内容页加载到PagedList(其实就是个list)中。PagedList可以随着加载更多数据而增长,但加载的数据无法更新。如果修改了基础数据集,则必须创建一个新的PagedList/DataSource对来表示新数据。

PagedList queries data from its DataSource in response to loading hints. PagedListAdapter calls PagedList.loadAround(int) to load content as the user scrolls in a RecyclerView.
To control how and when a PagedList queries data from its DataSource, see PagedList.Config. The Config object defines things like load sizes and prefetch distance.
PagedList从datasouce拿到数据,然后PagedListAdapter调用PagedList.loadAround(int)在用户在RecyclerView中滚动时加载内容。
而PagedList.Config对象定义了每次加载的大小和预加载数量等参数。
先看下 PagedList.Config 参数配置:
        // 每次分页加载的数量
        .setPageSize(pageSize)
        // 初始化时加载的数量(该值通常大于pageSize)
        .setInitialLoadSizeHint(initialLoadSizeHint)
        // 预加载的数量(默认和pageSize相等)
        //.setPrefetchDistance(prefetchDistance)
        // 数据源最大可加载的数量
        //.setMaxSize(maxSize)
        // 未加载出来的条目是否用占位符替代(默认true,启用的话必须设置maxSize)
        //.setEnablePlaceholders(false)
        .build()
To implement, extend one of the subclasses: PageKeyedDataSource, ItemKeyedDataSource, or PositionalDataSource.
由于datasource是一个抽象类,它的实现类有PageKeyedDataSource,ItemKeyedDataSource, or PositionalDataSource。

Use PageKeyedDataSource if pages you load embed keys for loading adjacent pages. For example a network response that returns some items, and a next/previous page links.
如果页面加载了用于加载相邻页面的key,请使用PageKeyedDataSource。例如,返回一些项目的网络响应,以及下一页/上一页信息。(也就是根页的信息key来请求数据的场景)

Use ItemKeyedDataSource if you need to use data from item N-1 to load item N. For example, if requesting the backend for the next comments in the list requires the ID or timestamp of the most recent loaded comment, or if querying the next users from a name-sorted database query requires the name and unique ID of the previous.
如果需要使用项N-1中的数据来加载项N,请使用ItemKeyedDataSource。key是依赖特定item中的信息。

Use PositionalDataSource if you can load pages of a requested size at arbitrary positions, and provide a fixed item count. PositionalDataSource supports querying pages at arbitrary positions, so can provide data to PagedLists in arbitrary order. Note that PositionalDataSource is required to respect page size for efficient tiling. If you want to override page size (e.g. when network page size constraints are only known at runtime), use one of the other DataSource classes.
如果可以在任意位置加载请求大小的页面,并提供固定的项目计数,请使用PositionalDataSource。也就是总数固定,通过特定的position位置加载数据,这里Key是Integer类型的position位置信息。
以上就是paging自带的几个常用的数据DataSource<Key,Value>. key是指加载数据的条件信息,value是指对应的数据实体类。

To page in data from a source that does provide updates, you can create a DataSource.Factory, where each DataSource created is invalidated when an update to the data set occurs that makes the current snapshot invalid. For example, when paging a query from the Database, and the table being queried inserts or removes items. You can also use a DataSource.Factory to provide multiple versions of network-paged lists. If reloading all content (e.g. in response to an action like swipe-to-refresh) is required to get a new version of data, you can connect an explicit refresh signal to call invalidate() on the current DataSource.
也就是说,可以通过DataSource.Factory来提供数据源,如果需要重新加载(例如:页面刷新),可以调用DataSource.invalidate().
val factory = object : DataSource.Factory<Any, T>() {
    override fun create(): DataSource<Any, T> {
         // createDataSource()
         // 数据来源可以是DB缓存,网络...
         // 创建的实例可以是PageKeyedDataSource/ItemKeyedDataSource/PositionalDataSource
    }
}
以ItemKeyedDataSource为例,跟PageKeyedDataSource一样,也是继承ContiguousDataSource(连续的数据源)的子类:
![ItemKeyedDataSource](http://img.mukewang.com/szimg/6375070d086bdb5506640107.jpg)
由于ItemKeyedDataSource也是抽象类,需要实现上面几个方法:
(1) getKey(item): 上面讲过,key是依赖特定item中的信息,一般为id.
(2) loadInitial(params,callback): 加载初始化数据。追溯源码 LivePagedListBuilder.build() —> create()—> ComputableLiveData.compute()—> PagedList.build()—> PagedList.create  —> new ContiguousPagedList() —> dispatchLoadInitial —> loadInitial
发现原来是LivePagedListBuilder执行build()方法时触发的。// 注意:compute()时会调用getKey().
(3) loadAfter(params,callback):向后加载分页数据。那么它是什么时候触发的呢(这个问题之前想了很久)?后来通过上面Google对DataSource的描述“PagedList queries data from its DataSource in response to loading hints. PagedListAdapter calls PagedList.loadAround(int) to load content as the user scrolls in a RecyclerView.”)才知,滑动的时候会调用PagedList.loadAround(int),而往上追溯发现是AsyncPagedListDiffer的getItem(int index)触发的,Google对这个AsyncPagedListDiffer类的描述是“Helper object for mapping a PagedList into a RecyclerView.Adapter.”,也就是用于将PagedList映射到RecyclerView.Adapter的Helper对象。这就大概明白了。
源码追溯:滑动会触发getItem(int)—> PageList.loadAround—> ContiguousPagedList.loadAroundInternal—> scheduleAppend—> (后台线程中执行)mDataSource.dispatchLoadAfter—> loadAfter(params,callback)
我们就可以在loadAfter(params,callback)做获取数据的操作(如拿本地缓存、网络数据等),那么数据是怎么传给AsyncPagedListDiffer这个对象的呢?这里面有个callback(只有一个回调方法onResult(data)),Google描述是“Called to pass loaded data from a DataSource.”,也就是调用以传递来自DataSource的加载的数据,即onResult(data)是用来传递ItemKeyedDataSource拿到的数据用的,
通过源码:
@Override
    final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
            int pageSize, @NonNull Executor mainThreadExecutor,
            @NonNull PageResult.Receiver<Value> receiver) {
        loadAfter(new LoadParams<>(getKey(currentEndItem), pageSize),
                new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
    }
可知,实际上callback传的是LoadCallbackImpl,type=PageResult.APPEND
static class LoadCallbackImpl<Value> extends LoadCallback<Value> {
        final LoadCallbackHelper<Value> mCallbackHelper;
        LoadCallbackImpl(@NonNull ItemKeyedDataSource dataSource, @PageResult.ResultType int type,
                @Nullable Executor mainThreadExecutor,
                @NonNull PageResult.Receiver<Value> receiver) {
            mCallbackHelper = new LoadCallbackHelper<>(
                    dataSource, type, mainThreadExecutor, receiver);
        }
        @Override
        public void onResult(@NonNull List<Value> data) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
            }
        }
    }
调用的是 mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0)),也就是ItemKeyedDataSource拿到的data数据传给了它,
void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
            Executor executor;
            synchronized (mSignalLock) {
            // 这个mHasSignalled只有这一个地方赋值
            // 也就说明这个dispatchResultToReceiver方法一旦执行过一次,
            // 就不允许再执行了,否则直接抛异常
                if (mHasSignalled) {
                    throw new IllegalStateException(
                            "callback.onResult already called, cannot call again.");
                }
                mHasSignalled = true;
                executor = mPostExecutor;
            }
            if (executor != null) {// 如果有设置mPostExecutor则异步执行
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                    // mResultType=PageResult.APPEND, result=data
                        mReceiver.onPageResult(mResultType, result);
                    }
                });
            } else {
                mReceiver.onPageResult(mResultType, result);
            }
        }
接着看mReceiver.onPageResult(mResultType, result);
PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
       //。。。
        @AnyThread
        @Override
        public void onPageResult(@PageResult.ResultType int resultType,
                @NonNull PageResult<V> pageResult) {
            // 数据无效(即空数据集合)
            if (pageResult.isInvalid()) {
            // Detach the PagedList from its DataSource, and attempt to load no more data.
            // 将PagedList与其DataSource分离,并尝试不再加载更多数据。
                detach();
                return;
            }
        // 如果PagedList与其DataSource分离(即不再有关联),则不管它
            if (isDetached()) {
                // No op, have detached
                return;
            }
            List<V> page = pageResult.page;
            // 如果是PageResult.INIT,也就是loadInitial回传过来的,
            if (resultType == PageResult.INIT) {
            // 那么mStorage(是个PagedStorage,专门存储分页数据的一个东东)将初始化分页数据。
            // 追溯源码:PageStorage.init—> callback.onInitialized(size())—> onInitialized—> onInitialized—> mUpdateCallback.onInserted(position, count)—> mAdapter.notifyItemRangeInserted(position, count)
            // 我的乖乖,最后还是通过把PagedList映射到RecyclerView.Adapter的Helper对象AsyncPagedListDiffer处理的,然后更新UI。
                mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
                        pageResult.positionOffset, ContiguousPagedList.this);
                if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
                    // Because the ContiguousPagedList wasn't initialized with a last load position,
                    // initialize it to the middle of the initial load
                    mLastLoad =
                            pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
                }
            } else {
                // if we end up trimming, we trim from side that's furthest from most recent access
                boolean trimFromFront = mLastLoad > mStorage.getMiddleOfLoadedRange();
                // is the new page big enough to warrant pre-trimming (i.e. dropping) it?
                boolean skipNewPage = mShouldTrim
                        && mStorage.shouldPreTrimNewPage(
                                mConfig.maxSize, mRequiredRemainder, page.size());
                if (resultType == PageResult.APPEND) {
                    if (skipNewPage && !trimFromFront) {// ?
                        // don't append this data, drop it
                        mAppendItemsRequested = 0;
                        mAppendWorkerState = READY_TO_FETCH;
                    } else {
                    // 追溯源码:PageStorage.appendPage—> callback.onPageAppended—> notifyInserted(endPosition + changedCount, addedCount)-->...--> mAdapter.notifyItemRangeInserted(position, count) 然后更新UI
                        mStorage.appendPage(page, ContiguousPagedList.this);
                    }
                } else if (resultType == PageResult.PREPEND) {
                    if (skipNewPage && trimFromFront) {
                        // don't append this data, drop it
                        mPrependItemsRequested = 0;
                        mPrependWorkerState = READY_TO_FETCH;
                    } else {
                    // 同理。。。
                        mStorage.prependPage(page, ContiguousPagedList.this);
                    }
                } else {
                    throw new IllegalArgumentException("unexpected resultType " + resultType);
                }
            }
            // 如果LivePagedListBuilder设置了boundaryCallback(边界回调)
            if (mBoundaryCallback != null) {
                //...
                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
            }
        }
    };
   
分发边界回调 dispatchBoundaryCallbacks:
void dispatchBoundaryCallbacks(boolean begin, boolean end) {
        // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present
        if (begin) {
            // 第一条数据被加载
            mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem());
        }
        if (end) {
            // 最后一条数据被加载
            mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem());
        }
    }
最后再看下前面说的DataSource.invalidate()干了啥?
源码追溯:onInvalidated —> invalidate()—> mInvalidationRunnable
final Runnable mInvalidationRunnable = new Runnable() {
        @MainThread//(发生在主线程)
        @Override
        public void run() {
            boolean isActive = mLiveData.hasActiveObservers();
            if (mInvalid.compareAndSet(false, true)) {//将livedata的mInvalid 有效--> 无效
                if (isActive) {// 有活跃的观察者,执行mRefreshRunnable
                    mExecutor.execute(mRefreshRunnable);
                }
            }
        }
    };
// 子线程中执行
final Runnable mRefreshRunnable = new Runnable() {
        @WorkerThread
        @Override
        public void run() {
            boolean computed;
            do {
                computed = false;
                // compute can happen only in 1 thread but no reason to lock others.
                if (mComputing.compareAndSet(false, true)) {
                    // as long as it is invalid, keep computing.
                    try {
                        T value = null;
                        // livedata的mInvalid 无效-> 有效
                        // compareAndSet原子操作,没成功一直执行
                        while (mInvalid.compareAndSet(true, false)) {
                            computed = true;
                            // 这里compute()方法会重新创建一对新的PagedList、datasource
                            // 又会执行PagedList.build()方法触发loadInitial 重新刷新页面骚操作
                            value = compute();
                        }
                        if (computed) {
                          // 触发liveData观察者的异步onChanged()回调
                            mLiveData.postValue(value);
                        }
                    } finally {
                        // release compute lock
                        mComputing.set(false);
                    }
                }
        //...
            } while (computed && mInvalid.get());
        }
    };
    

DataSource 小结:
1. DataSource就是个提供数据的窗口,一般有三种,常用的就ItemKeyedDataSource。
2. AsyncPagedListDiffer是一个将PagedList映射到RecyclerView.Adapter的Helper对象。
3. callback.onResult(data)是用来回传DataSource拿到的数据。具体流程:数据有经过包装给了dispatchResultToReceiver,接着触发了mReceiver.onPageResult(mResultType, result)….最终触发了mAdapter.notifyItemRangeInserted(position, count)刷新界面。
4. 两个触发loadInitial()的地方:一个是LivePagedListBuilder.create(),第二个是DataSource.invalidate(),因为这两个都会触发mRefreshRunnable执行,进而调用compute()实例化PagedList(PagedList.build()--> PagedList.create)/DataSource,这俩货是成对出现且PagedList.Builder持有DataSource对象。

2)PagedList

All data in a PagedList is loaded from its DataSource.Creating a PagedList loads the first chunk of data from the DataSource immediately, and should for this reason be done on a background thread. 
也就是说,PagedList中的所有数据都从其DataSource加载的。创建PagedList会立即从DataSource加载第一块数据(这个在上面讲过PagedList.create时会调用compute()触发loadInitial()执行)

A PagedList initially presents this first partial load as its content, and expands over time as content is loaded in. When loadAround is called, items will be loaded in near the passed list index. If placeholder nulls are present in the list, they will be replaced as content is loaded. If not, newly loaded items will be inserted at the beginning or end of the list.
PagedList最初将第一个部分加载作为其内容,并随着内容的加载而扩展。当调用loadAround时,将在传递的列表索引附近加载项。如果列表中存在占位符空值,则它们将在加载内容时被替换。如果没有,新加载的项目将插入列表的开头或结尾。

PagedList can present data for an unbounded, infinite scrolling list, or a very large but countable list. Use PagedList.Config to control how many items a PagedList loads, and when.
PagedList可以为无限滚动列表或非常大但可计数的列表显示数据。使用PagedList.Config可以控制PagedList加载参数。

If you use LivePagedListBuilder to get a androidx.lifecycle.LiveData<PagedList>, it will initialize PagedLists on a background thread for you.
也就是使用LivePagedListBuilder将PagedList作为泛型参数传递给“这个可感知生命周期”的消息订阅组件LiveData,并且LivePagedListBuilder.build时在后台线程初始化PageList。
先看下这个LivePagedListBuilder.build干了啥:
 @NonNull
    @SuppressLint("RestrictedApi")
    public LiveData<PagedList<Value>> build() {
    // 传入几个参数:初始化加载的key,PagedList.Config,边界回调callback,DataSource.Factory,
    // notifyExecutor(接收pagedlist更新),mFetchExecutor(指定从哪个线程池获取分页数据)
        return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
                ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
    }
    
原来是创建了一个LiveData<PagedList<T>>,这一下就把PagedList和LiveData关联起来了。
@AnyThread
    @NonNull
    @SuppressLint("RestrictedApi")
    // 注意这是个私有方法,仅供LivePagedListBuilder.build()调用
    private static <Key, Value> LiveData<PagedList<Value>> create(
            @Nullable final Key initialLoadKey,
            @NonNull final PagedList.Config config,
            @Nullable final PagedList.BoundaryCallback boundaryCallback,
            @NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
            @NonNull final Executor notifyExecutor,
            @NonNull final Executor fetchExecutor) {
        return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
            @Nullable
            private PagedList<Value> mList;
            @Nullable
            private DataSource<Key, Value> mDataSource;
            private final DataSource.InvalidatedCallback mCallback =
                    new DataSource.InvalidatedCallback() {
                        @Override
                        public void onInvalidated() {
                            invalidate();
                        }
                    };
                   
            @SuppressWarnings("unchecked") // for casting getLastKey to Key
            @Override
            // 注意这个ComputableLiveData对象的方法compute(),
            // 它用于创建 PagedList/DataSource,
            // 也是触发上面说的DataSource.loadInitial()执行的核心。
            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);
                    // 构建PagedList对象
                    mList = new PagedList.Builder<>(mDataSource, config)
                            .setNotifyExecutor(notifyExecutor)
                            .setFetchExecutor(fetchExecutor)
                            .setBoundaryCallback(boundaryCallback)
                            .setInitialKey(initializeKey)
                            .build();// 触发dispatchLoadInitial执行
                } while (mList.isDetached());// 保证PagedList和mDataSource始终是一对
                return mList;
            }
        }.getLiveData();//注意返回的是livedata对象,不是ComputableLiveData对象哦
    }

大致逻辑:ComputableLiveData是一个持有mLiveData对象抽象类,通过暴露compute方法,LivePagedListBuilder为其提供创建 PagedList/DataSource对象实例化的实现,并对ComputableLiveData的mRefreshRunnable、mInvalidationRunnable的实现细节进行隐藏,仅暴露一个它持有的mLiveData对象。这样一来,外界只需要关心这个泛型为PagedList的LiveData对象,PagedList对外将毫无存在感,就是个存放来自DataSource数据的list罢了,这样LiveData的逼格又上个了台阶。

3)PagedListAdapter

RecyclerView.Adapter base class for presenting paged data from PagedLists in a RecyclerView.This class is a convenience wrapper around AsyncPagedListDiffer that implements common default behavior for item counting, and listening to PagedList update callbacks.
它是用于在RecyclerView中显示PagedList中的分页数据的适配器基类,同时持有AsyncPagedListDiffer(前面讲过AsyncPagedListDiffer是一个将PagedList映射到RecyclerView.Adapter的Helper对象)对象并对其常用方法进行包装,使得它成为一个方便处理PagedList数据的适配器。

While using a LiveData<PagedList> is an easy way to provide data to the adapter, it isn't required - you can use submitList(PagedList) when new lists are available.
虽然使用LiveData<PagedList>是向适配器提供数据的一种简单方法,但这不是必需的——当有新列表可用时,可以使用submitList(PagedList)。

PagedListAdapter listens to PagedList loading callbacks as pages are loaded, and uses DiffUtil on a background thread to compute fine grained updates as new PagedLists are received.
PagedListAdapter在加载页面时侦听PagedList加载回调,并在收到新PagedList时在后台线程上使用DiffUtil计算细粒度更新。

Handles both the internal paging of the list as more data is loaded, and updates in the form of new PagedLists.
在加载更多数据时处理列表的内部分页,并以新PagedList的形式进行更新。
PagedListAdapter在构造时,需要传递一个DiffUtil.ItemCallback<T>参数,用于计算列表中两个非空项之间差异的回调。
protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
        mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
        // 监听当前PagedList更新
        mDiffer.addPagedListListener(mListener);
}
要实现的DiffUtil.ItemCallback的2个方法:
// 调用以检查两个对象是否表示同一项,例如,如果您的项具有唯一的id,则判断id是否一致
areItemsTheSame(@NonNull T oldItem, @NonNull T newItem)
// 调用以检查两个项是否具有相同的数据。这就得重写equals方法了。
areContentsTheSame(@NonNull T oldItem, @NonNull T newItem)
由于PagedListAdapter继承的是RecycleView.Adapter, 同时它本来也是个抽象类,所以子类XXPagedListAdapter需要实现的方法有:
// 创建 ViewHolder(负责承载每个item的布局)
// 当然也可以复写getItemViewType(position)来定义多种viewType
VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
// 绑定 ViewHolder (负责将每个item的holder绑定数据)
// 这里holder就是onCreateViewHolder拿到的VH(可以复用的对象),postion为当前PagedList的索引
void onBindViewHolder(@NonNull VH holder, int position) 

再来谈谈说上面说的submitList(pagedList)方法:
public void submitList(@Nullable final PagedList<T> pagedList) {
        submitList(pagedList, null);
}
// 将新的PagedList传递给AsyncPagedListDiffer对象。
// 如果PagedList已经存在,将在后台线程上异步计算diff。当计算diff时,它将被应用(调度到ListUpdateCallback),新的PagedList将作为当前列表交换。
public void submitList(@Nullable final PagedList<T> pagedList,
            @Nullable final Runnable commitCallback) {
        if (pagedList != null) {
             // 第一次提交
            if (mPagedList == null && mSnapshot == null) {
                mIsContiguous = pagedList.isContiguous();
            } else {
            // 必须保证每次提交的PagedList要么都是连续型的,要么都是非连续的。
                if (pagedList.isContiguous() != mIsContiguous) {
                    throw new IllegalArgumentException("AsyncPagedListDiffer cannot handle both"
                            + " contiguous and non-contiguous lists.");
                }
            }
        }
        // incrementing generation means any currently-running diffs are discarded when they finish
        final int runGeneration = ++mMaxScheduledGeneration;
        // 跟之前一样,啥也不做
        if (pagedList == mPagedList) {
            // nothing to do (Note - still had to inc generation, since may have ongoing work)
            if (commitCallback != null) {
                commitCallback.run();//不管有没有处理,都回传一下
            }
            return;
        }
       // mSnapshot可以理解为mPagedList的副本
        final PagedList<T> previous = (mSnapshot != null) ? mSnapshot : mPagedList;
        if (pagedList == null) {// 如果传入的为null
            int removedCount = getItemCount();
            if (mPagedList != null) {// 而之前的不为null,
               // 移除分页加载回调监听,
               // 这样后面就不会再触发onInserted/onRemoved/onChanged这些方法执行了
                mPagedList.removeWeakCallback(mPagedListCallback);
                mPagedList = null;
            } else if (mSnapshot != null) {
                mSnapshot = null;
            }
            // 则移除之前加载的所有数据
            // dispatch update callback after updating mPagedList/mSnapshot
            mUpdateCallback.onRemoved(0, removedCount);
            // 并通知当前列表更新
            onCurrentListChanged(previous, null, commitCallback);
            return;
        }
        // pagedList有数据,而mPagedList没数据 副本也没有,说明是第一次
        if (mPagedList == null && mSnapshot == null) {
            // fast simple first insert
            mPagedList = pagedList;
            pagedList.addWeakCallback(null, mPagedListCallback);
            // adapter从第一行开始插入数据, 更新ui
            // dispatch update callback after updating mPagedList/mSnapshot
            mUpdateCallback.onInserted(0, pagedList.size());
            onCurrentListChanged(null, pagedList, commitCallback);
            return;
        }
        // pagedList有数据,mPagedList也有数据
        if (mPagedList != null) {
            // first update scheduled on this list, so capture mPages as a snapshot, removing
            // callbacks so we don't have resolve updates against a moving target
            mPagedList.removeWeakCallback(mPagedListCallback);
            // 因为需要在异步线程池中比较,所以先拿一个之前的副本
            mSnapshot = (PagedList<T>) mPagedList.snapshot();
            mPagedList = null;
        }
        if (mSnapshot == null || mPagedList != null) {
            throw new IllegalStateException("must be in snapshot state to diff");
        }
        final PagedList<T> oldSnapshot = mSnapshot;
        final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
        // 计算是否需要更新数据给adapter
        mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                final DiffUtil.DiffResult result;
                result = PagedStorageDiffHelper.computeDiff(
                        oldSnapshot.mStorage,
                        newSnapshot.mStorage,
                        mConfig.getDiffCallback());
                 ...
            }
        });
    }

这样基本清楚了:

PagedList :存放来自DataSource数据的实体集合;

LiveData> livedata:添加了监听数据能力;

xxViewModel.livedata:又添加了数据共享能力。

基本调用:xxViewModel.livedata.observe(viewLifecycleOwner) { pagedList -> submitList(pagedList) }

(三)小结

1: 由LivePagedListBuilder.build的可知,DataSource是与PageList同时创建且相互关联的关系;

2: 又通过mRecycleView.adapter = pagedListAdapter, 再传给RecycleView,这样pagedListAdapter就与RecycleView进行了绑定;

3:最后 PagedListAdapter借助内部的AsyncPagedListDiffer来处理PagedList对象数据,而具体的数据data列表则由DataSource的loadXXX()接口被动触发获取并回传onResult(data列表)…最后由AsyncPagedListDiffer.mUpdateCallback触发ui更新。(透过源码可以看出,如果没有AsyncPagedListDiffer,这个PagedListAdapter就是一个普通的Adapter。)

借助AsyncPagedListDiffer&LivePagedListBuilder这两个纽带,就把PagedListAdapter、DataSource、PageList三者串起来了。Paging 组件就是谷歌用来设计给RecyclerView优化分页加载用的。

 

 类似资料: