Paging Library源码浅析

首先还是放上官方文档地址:developer.android.google.cn/topic/libra…

如果你还不知道这个库是可以做什么事,可以看官方文档事例或者参考我之前写的这篇文章:

juejin.im/post/5a066b….

目前该库还处于测试阶段,所以我就按上篇文章的内容进行部分简单的源码分析,从中你应该可以了解到该库的设计思路.

直接进入正题,先写简单的demo,首先创建我们的model.

MainData:

public class MainData extends AndroidViewModel {

    /**
     * 每次需要10个数据.
     */
    private static final int NEED_NUMBER = 10;

    /**
     * 福利第一页.
     */
    private static final int PAGE_FIRST = 1;

    /**
     * 分页.
     */
    private int mPage = PAGE_FIRST;


    /**
     * 列表数据.
     */
    private LiveData<PagedList<GankData>> mDataLiveData;

    public MainData(@NonNull Application application) {
        super(application);
    }

    public LiveData<PagedList<GankData>> getDataLiveData() {
        initPageList();
        return mDataLiveData;
    }

    /**
     * 初始化pageList.
     */
    private void initPageList() {
        //获取dataSource,列表数据都从这里获取,
        final DataSource<Integer, GankData> tiledDataSource = new TiledDataSource<GankData>() {
            /**
             * 需要的总个数,如果数量不定,就传COUNT_UNDEFINED.
             */
            @Override
            public int countItems() {
                return DataSource.COUNT_UNDEFINED;
            }

            /**
             * 返回需要加载的数据.
             * 这里是在线程异步中执行的,所以我们可以同步请求数据并且返回
             * @param startPosition 现在第几个数据
             * @param count 加载的数据数量
             */
            @Override
            public List<GankData> loadRange(int startPosition, int count) {
                List<GankData> gankDataList = new ArrayList<>();

                //这里我们用retrofit获取数据,每次获取十条数据,数量不为空,则让mPage+1
                try {
                    Response<BaseResponse<List<GankData>>> execute = RetrofitApi.getInstance().mRetrofit.create(AppService.class)
                            .getWelfare1(mPage, NEED_NUMBER).execute();
                    gankDataList.addAll(execute.body().getResults());

                    if (!gankDataList.isEmpty()) {
                        mPage++;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

                return gankDataList;
            }
        };

        //这里我们创建LiveData<PagedList<GankData>>数据,
        mDataLiveData = new LivePagedListProvider<Integer, GankData>() {

            @Override
            protected DataSource<Integer, GankData> createDataSource() {
                return tiledDataSource;
            }
        }.create(0, new PagedList.Config.Builder()
                .setPageSize(NEED_NUMBER) //每次加载的数据数量
                //距离本页数据几个时候开始加载下一页数据(例如现在加载10个数据,设置prefetchDistance为2,则滑到第八个数据时候开始加载下一页数据).
                .setPrefetchDistance(NEED_NUMBER)
                //这里设置是否设置PagedList中的占位符,如果设置为true,我们的数据数量必须固定,由于网络数据数量不固定,所以设置false.
                .setEnablePlaceholders(false)
                .build());
    }
}复制代码

这里我们需要创建LiveData<PagedList<GankData>>这个对象,对于LiveData之前已经讲过了,如果你还不知道这个原理,可以去看下我之前文章:juejin.im/post/5a03ed…

首先我们来看下PageList到底是什么:首先PageList是个抽象类,并且提供Build模式填充数据,首先看下它的build类:

public static class Builder<Key, Value> {
    private DataSource<Key, Value> mDataSource;
    private Executor mMainThreadExecutor;
    private Executor mBackgroundThreadExecutor;
    private Config mConfig;
    private Key mInitialKey;

    /**
     * Creates a {@link PagedList} with the given parameters.
     * <p>
     * This call will initial data and perform any counting needed to initialize the PagedList,
     * therefore it should only be called on a worker thread.
     * <p>
     * While build() will always return a PagedList, it's important to note that the PagedList
     * initial load may fail to acquire data from the DataSource. This can happen for example if
     * the DataSource is invalidated during its initial load. If this happens, the PagedList
     * will be immediately {@link PagedList#isDetached() detached}, and you can retry
     * construction (including setting a new DataSource).
     *
     * @return The newly constructed PagedList
     */
    @WorkerThread
    @NonNull
    public PagedList<Value> build() {
        if (mDataSource == null) {
            throw new IllegalArgumentException("DataSource required");
        }
        if (mMainThreadExecutor == null) {
            throw new IllegalArgumentException("MainThreadExecutor required");
        }
        if (mBackgroundThreadExecutor == null) {
            throw new IllegalArgumentException("BackgroundThreadExecutor required");
        }
        if (mConfig == null) {
            throw new IllegalArgumentException("Config required");
        }

        return PagedList.create(
                mDataSource,
                mMainThreadExecutor,
                mBackgroundThreadExecutor,
                mConfig,
                mInitialKey);
    }复制代码

这里我们看到必须填的数据有至少有四个,第一个DataSource,不用说,自己手动要创建的,两个线程池,一个Config和一个key,我们再来看下Config:

public static class Config {
    final int mPageSize;
    final int mPrefetchDistance;
    final boolean mEnablePlaceholders;
    final int mInitialLoadSizeHint;

    private Config(int pageSize, int prefetchDistance,
            boolean enablePlaceholders, int initialLoadSizeHint) {
        mPageSize = pageSize;
        mPrefetchDistance = prefetchDistance;
        mEnablePlaceholders = enablePlaceholders;
        mInitialLoadSizeHint = initialLoadSizeHint;
    }

    /**
     * Builder class for {@link Config}.
     * <p>
     * You must at minimum specify page size with {@link #setPageSize(int)}.
     */
    public static class Builder {
        private int mPageSize = -1;
        private int mPrefetchDistance = -1;
        private int mInitialLoadSizeHint = -1;
        private boolean mEnablePlaceholders = true;

        /**
         * Creates a {@link Config} with the given parameters.
         *
         * @return A new Config.
         */
        public Config build() {
            if (mPageSize < 1) {
                throw new IllegalArgumentException("Page size must be a positive number");
            }
            if (mPrefetchDistance < 0) {
                mPrefetchDistance = mPageSize;
            }
            if (mInitialLoadSizeHint < 0) {
                mInitialLoadSizeHint = mPageSize * 3;
            }
            if (!mEnablePlaceholders && mPrefetchDistance == 0) {
                throw new IllegalArgumentException("Placeholders and prefetch are the only ways"
                        + " to trigger loading of more data in the PagedList, so either"
                        + " placeholders must be enabled, or prefetch distance must be > 0.");
            }

            return new Config(mPageSize, mPrefetchDistance,
                    mEnablePlaceholders, mInitialLoadSizeHint);
        }
    }复制代码

这里同样采用Build模式,看下参数:mPageSize代表每次加载数量,mPrefetchDistance代表距离最后多少item数量开始加载下一页,mInittialLoadSizeHint代表首次加载数量(但是需要配合KeyedDataSource,我们现在用TiledDataSource,所以忽略这个属性),mEnablePlaceholders代表是否设置null占位符,需要加载固定数量时候可以设置为true,如果数量不固定则设为false.

因为后面涉及到DataSource,所以我们接下来先分析TiledDataSource.

TiledDataSource的父类DataSource.

// Since we currently rely on implementation details of two implementations,
// prevent external subclassing, except through exposed subclasses
DataSource() {
}

/**
 * 数量不定的标志.
 */
@SuppressWarnings("WeakerAccess")
public static int COUNT_UNDEFINED = -1;

/**
 * 总数量(数量不定返回COUNT_UNDEFINED).
 */
@WorkerThread
public abstract int countItems();

/**
 * Returns true if the data source guaranteed to produce a contiguous set of items,
 * never producing gaps.
 */
abstract boolean isContiguous();

/**
 * Invalidation callback for DataSource.
 * <p>
 * Used to signal when a DataSource a data source has become invalid, and that a new data source
 * is needed to continue loading data.
 */
public interface InvalidatedCallback {
    /**
     * Called when the data backing the list has become invalid. This callback is typically used
     * to signal that a new data source is needed.
     * <p>
     * This callback will be invoked on the thread that calls {@link #invalidate()}. It is valid
     * for the data source to invalidate itself during its load methods, or for an outside
     * source to invalidate it.
     */
    @AnyThread
    void onInvalidated();
}

/**
 * 数据可用的标志.
 */
private AtomicBoolean mInvalid = new AtomicBoolean(false);

/**
 * 回调的线程安全集合.
 */
private CopyOnWriteArrayList<InvalidatedCallback> mOnInvalidatedCallbacks =
        new CopyOnWriteArrayList<>();

/**
 * Add a callback to invoke when the DataSource is first invalidated.
 * <p>
 * Once invalidated, a data source will not become valid again.
 * <p>
 * A data source will only invoke its callbacks once - the first time {@link #invalidate()}
 * is called, on that thread.
 *
 * @param onInvalidatedCallback The callback, will be invoked on thread that
 *                              {@link #invalidate()} is called on.
 */
@AnyThread
@SuppressWarnings("WeakerAccess")
public void addInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) {
    mOnInvalidatedCallbacks.add(onInvalidatedCallback);
}

/**
 * Remove a previously added invalidate callback.
 *
 * @param onInvalidatedCallback The previously added callback.
 */
@AnyThread
@SuppressWarnings("WeakerAccess")
public void removeInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) {
    mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
}

/**
 * 对外方法,执行回调(但是未发现任何地方调用了这个方法,估计是为了以后扩展用).
 */
@AnyThread
public void invalidate() {
    if (mInvalid.compareAndSet(false, true)) {
        for (InvalidatedCallback callback : mOnInvalidatedCallbacks) {
            callback.onInvalidated();
        }
    }
}

/**
 * Returns true if the data source is invalid, and can no longer be queried for data.
 *
 * @return True if the data source is invalid, and can no longer return data.
 */
@WorkerThread
public boolean isInvalid() {
    return mInvalid.get();
}复制代码

这里需要先说下AtomicBoolean和CopyOrWriteArrayList,

AtomicBoolean:按照文档说明,大概意思就是以原子的方式更新bollean值,其中

  • compareAndSet(boolean expect, boolean update)这个方法,
  • 这个方法主要两个作用 1. 比较AtomicBoolean和expect的值,如果一致,执行方法内的语句。其实就是一个if语句 2. 把AtomicBoolean的值设成update 比较最要的是这两件事是一气呵成的,这连个动作之间不会被打断,任何内部或者外部的语句都不可能在两个动作之间运行。为多线程的控制提供了解决的方案。

CopyOrWriteArrayList是一个线程安全的list,它的 add和remove等一些方法都是加锁了.

再来看下TiledDataSource:

public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {

    @WorkerThread
    @Override
    public abstract int countItems();

    @Override
    boolean isContiguous() {
        return false;
    }

    @WorkerThread
    public abstract List<Type> loadRange(int startPosition, int count);

    final List<Type> loadRangeWrapper(int startPosition, int count) {
        if (isInvalid()) {
            return null;
        }
        List<Type> list = loadRange(startPosition, count);
        if (isInvalid()) {
            return null;
        }
        return list;
    }

    ContiguousDataSource<Integer, Type> getAsContiguous() {
        return new TiledAsBoundedDataSource<>(this);
    }

    /**
     * BoundedDataSource是最终继承ContiguousDataSource的类,这个负责包装TiledDataSource.
     */
    static class TiledAsBoundedDataSource<Value> extends BoundedDataSource<Value> {
        final TiledDataSource<Value> mTiledDataSource;

        TiledAsBoundedDataSource(TiledDataSource<Value> tiledDataSource) {
            mTiledDataSource = tiledDataSource;
        }

        @WorkerThread
        @Nullable
        @Override
        public List<Value> loadRange(int startPosition, int loadCount) {
            return mTiledDataSource.loadRange(startPosition, loadCount);
        }
    }
}复制代码

loadRange方法我们用不到暂时(没发现调用这个方法的地方),我们只需要实现countItems和loadRange方法,我们例子中是无限制数量,所以传了-1,而loadRange方法是在异步线程中执行,所以这里可以用网络同步请求返回数据.

然后接下来看PagedList的create方法.

@NonNull
private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
        @NonNull Executor mainThreadExecutor,
        @NonNull Executor backgroundThreadExecutor,
        @NonNull Config config,
        @Nullable K key) {
    if (dataSource.isContiguous() || !config.mEnablePlaceholders) {
        if (!dataSource.isContiguous()) {
            //noinspection unchecked
            dataSource = (DataSource<K, T>) ((TiledDataSource<T>) dataSource).getAsContiguous();
        }
        ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
        return new ContiguousPagedList<>(contigDataSource,
                mainThreadExecutor,
                backgroundThreadExecutor,
                config,
                key);
    } else {
        return new TiledPagedList<>((TiledDataSource<T>) dataSource,
                mainThreadExecutor,
                backgroundThreadExecutor,
                config,
                (key != null) ? (Integer) key : 0);
    }
}
复制代码

根据一系列条件,我们获取的PagedList是ContiguousPagedList.

然后我们可以来看LiveData<PagedList<GankData>>是怎么创建的了:

来看下LivePagedListProvider:

public abstract class LivePagedListProvider<Key, Value> {

    /**
     * Construct a new data source to be wrapped in a new PagedList, which will be returned
     * through the LiveData.
     *
     * @return The data source.
     */
    @WorkerThread
    protected abstract DataSource<Key, Value> createDataSource();

    /**
     * Creates a LiveData of PagedLists, given the page size.
     * <p>
     * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
     * {@link android.support.v7.widget.RecyclerView}.
     *
     * @param initialLoadKey Initial key used to load initial data from the data source.
     * @param pageSize Page size defining how many items are loaded from a data source at a time.
     *                 Recommended to be multiple times the size of item displayed at once.
     *
     * @return The LiveData of PagedLists.
     */
    @AnyThread
    @NonNull
    public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) {
        return create(initialLoadKey,
                new PagedList.Config.Builder()
                        .setPageSize(pageSize)
                        .build());
    }

    /**
     * Creates a LiveData of PagedLists, given the PagedList.Config.
     * <p>
     * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
     * {@link android.support.v7.widget.RecyclerView}.
     *
     * @param initialLoadKey Initial key to pass to the data source to initialize data with.
     * @param config PagedList.Config to use with created PagedLists. This specifies how the
     *               lists will load data.
     *
     * @return The LiveData of PagedLists.
     */
    @AnyThread
    @NonNull
    public LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey,
            final PagedList.Config config) {
        return new ComputableLiveData<PagedList<Value>>() {
            @Nullable
            private PagedList<Value> mList;
            @Nullable
            private DataSource<Key, Value> mDataSource;

            private final DataSource.InvalidatedCallback mCallback =
                    new DataSource.InvalidatedCallback() {
                @Override
                public void onInvalidated() {
                    invalidate();
                }
            };

            @Override
            protected PagedList<Value> compute() {
                @Nullable Key initializeKey = initialLoadKey;
                if (mList != null) {
                    //noinspection unchecked
                    initializeKey = (Key) mList.getLastKey();
                }

                do {
                    if (mDataSource != null) {
                        mDataSource.removeInvalidatedCallback(mCallback);
                    }

                    mDataSource = createDataSource();
                    mDataSource.addInvalidatedCallback(mCallback);

                    mList = new PagedList.Builder<Key, Value>()
                            .setDataSource(mDataSource)
                            .setMainThreadExecutor(ArchTaskExecutor.getMainThreadExecutor())
                            .setBackgroundThreadExecutor(
                                    ArchTaskExecutor.getIOThreadExecutor())
                            .setConfig(config)
                            .setInitialKey(initializeKey)
                            .build();
                } while (mList.isDetached());
                return mList;
            }
        }.getLiveData();
    }
复制代码

代码很简单(忽略那些mCallback,现阶段完全用不到),只要我们重写传入DataSource,创建的主要类是ComputableLiveData干得,看一下:

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class ComputableLiveData<T> {

    private final LiveData<T> mLiveData;

    private AtomicBoolean mInvalid = new AtomicBoolean(true);
    private AtomicBoolean mComputing = new AtomicBoolean(false);

    /**
     * Creates a computable live data which is computed when there are active observers.
     * <p>
     * It can also be invalidated via {@link #invalidate()} which will result in a call to
     * {@link #compute()} if there are active observers (or when they start observing)
     */
    @SuppressWarnings("WeakerAccess")
    public ComputableLiveData() {
        mLiveData = new LiveData<T>() {
            @Override
            protected void onActive() {
                // TODO if we make this class public, we should accept an executor
                ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
            }
        };
    }

    /**
     * Returns the LiveData managed by this class.
     *
     * @return A LiveData that is controlled by ComputableLiveData.
     */
    @SuppressWarnings("WeakerAccess")
    @NonNull
    public LiveData<T> getLiveData() {
        return mLiveData;
    }

    /**
     * mInvalid默认为true,这里可以一条线执行下去,PagedList就是上面那个compute里新建的对象,
     * 并且执行了postValue方法,观察者那边可以就收到通知了.
     */
    @VisibleForTesting
    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;
                        while (mInvalid.compareAndSet(true, false)) {
                            computed = true;
                            value = compute();
                        }
                        if (computed) {
                            mLiveData.postValue(value);
                        }
                    } finally {
                        // release compute lock
                        mComputing.set(false);
                    }
                }
                // check invalid after releasing compute lock to avoid the following scenario.
                // Thread A runs compute()
                // Thread A checks invalid, it is false
                // Main thread sets invalid to true
                // Thread B runs, fails to acquire compute lock and skips
                // Thread A releases compute lock
                // We've left invalid in set state. The check below recovers.
            } while (computed && mInvalid.get());
        }
    };

    // invalidation check always happens on the main thread
    @VisibleForTesting
    final Runnable mInvalidationRunnable = new Runnable() {
        @MainThread
        @Override
        public void run() {
            boolean isActive = mLiveData.hasActiveObservers();
            if (mInvalid.compareAndSet(false, true)) {
                if (isActive) {
                    // TODO if we make this class public, we should accept an executor.
                    ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
                }
            }
        }
    };
}
复制代码

代码也是很简单的,先看构造方法,构造方法中直接新建了LiveData,并且在生命周期onStrart后执行mRefreshRunable线程,这个线程直接给LiveData赋值,然后activity注册的地方就可以收到PagedList的回调了.

我们再来看看构造PagedList的默认线程池,追踪代码:

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class DefaultTaskExecutor extends TaskExecutor {
    private final Object mLock = new Object();
    private ExecutorService mDiskIO = Executors.newFixedThreadPool(2);

    @Nullable
    private volatile Handler mMainHandler;

    @Override
    public void executeOnDiskIO(Runnable runnable) {
        mDiskIO.execute(runnable);
    }

    @Override
    public void postToMainThread(Runnable runnable) {
        if (mMainHandler == null) {
            synchronized (mLock) {
                if (mMainHandler == null) {
                    mMainHandler = new Handler(Looper.getMainLooper());
                }
            }
        }
        //noinspection ConstantConditions
        mMainHandler.post(runnable);
    }

    @Override
    public boolean isMainThread() {
        return Looper.getMainLooper().getThread() == Thread.currentThread();
    }
}
复制代码

就是这个Executors.newFixedThreadPool(2)线程池了,

接下来我们看PagedList:

public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> {
    private final PagedListAdapterHelper<T> mHelper;

    /**
     * Creates a PagedListAdapter with default threading and
     * {@link android.support.v7.util.ListUpdateCallback}.
     *
     * Convenience for {@link #PagedListAdapter(ListAdapterConfig)}, which uses default threading
     * behavior.
     *
     * @param diffCallback The {@link DiffCallback} instance to compare items in the list.
     */
    protected PagedListAdapter(@NonNull DiffCallback<T> diffCallback) {
        mHelper = new PagedListAdapterHelper<>(this, diffCallback);
    }

    @SuppressWarnings("unused, WeakerAccess")
    protected PagedListAdapter(@NonNull ListAdapterConfig<T> config) {
        mHelper = new PagedListAdapterHelper<>(new ListAdapterHelper.AdapterCallback(this), config);
    }

    /**
     * Set the new list to be displayed.
     * <p>
     * If a list is already being displayed, a diff will be computed on a background thread, which
     * will dispatch Adapter.notifyItem events on the main thread.
     *
     * @param pagedList The new list to be displayed.
     */
    public void setList(PagedList<T> pagedList) {
        mHelper.setList(pagedList);
    }

    @Nullable
    protected T getItem(int position) {
        return mHelper.getItem(position);
    }

    @Override
    public int getItemCount() {
        return mHelper.getItemCount();
    }

    /**
     * Returns the list currently being displayed by the Adapter.
     * <p>
     * This is not necessarily the most recent list passed to {@link #setList(PagedList)}, because a
     * diff is computed asynchronously between the new list and the current list before updating the
     * currentList value.
     *
     * @return The list currently being displayed.
     */
    @Nullable
    public PagedList<T> getCurrentList() {
        return mHelper.getCurrentList();
    }
}复制代码

可以看到,PagedListAdapter只是个壳,做事的是PagedListAdapterHelper.

我们主要看PagedListAdapterHelper的这个几个方法:

public void setList(final PagedList<T> pagedList) {
    if (pagedList != null) {
        if (mList == null) {
            mIsContiguous = pagedList.isContiguous();
        } else {
            if (pagedList.isContiguous() != mIsContiguous) {
                throw new IllegalArgumentException("AdapterHelper cannot handle both contiguous"
                        + " and non-contiguous lists.");
            }
        }
    }

    if (pagedList == mList) {
        // nothing to do
        return;
    }

    // incrementing generation means any currently-running diffs are discarded when they finish
    final int runGeneration = ++mMaxScheduledGeneration;

    if (pagedList == null) {
        mUpdateCallback.onRemoved(0, mList.size());
        mList.removeWeakCallback(mPagedListCallback);
        mList = null;
        return;
    }

    if (mList == null) {
        // fast simple first insert
        mUpdateCallback.onInserted(0, pagedList.size());
        mList = pagedList;
        pagedList.addWeakCallback(null, mPagedListCallback);
        return;
    }

    if (!mList.isImmutable()) {
        // 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
        mList.removeWeakCallback(mPagedListCallback);
        mList = (PagedList<T>) mList.snapshot();
    }

    final PagedList<T> oldSnapshot = mList;
    final List<T> newSnapshot = pagedList.snapshot();
    mUpdateScheduled = true;
    mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
        @Override
        public void run() {
            final DiffUtil.DiffResult result;
            if (mIsContiguous) {
                result = ContiguousDiffHelper.computeDiff(
                        (NullPaddedList<T>) oldSnapshot, (NullPaddedList<T>) newSnapshot,
                        mConfig.getDiffCallback(), true);
            } else {
                result = SparseDiffHelper.computeDiff(
                        (PageArrayList<T>) oldSnapshot, (PageArrayList<T>) newSnapshot,
                        mConfig.getDiffCallback(), true);
            }

            mConfig.getMainThreadExecutor().execute(new Runnable() {
                @Override
                public void run() {
                    if (mMaxScheduledGeneration == runGeneration) {
                        mUpdateScheduled = false;
                        latchPagedList(pagedList, newSnapshot, result);
                    }
                }
            });
        }
    });
}

private void latchPagedList(
        PagedList<T> newList, List<T> diffSnapshot,
        DiffUtil.DiffResult diffResult) {
    if (mIsContiguous) {
        ContiguousDiffHelper.dispatchDiff(mUpdateCallback,
                (NullPaddedList<T>) mList, (ContiguousPagedList<T>) newList, diffResult);
    } else {
        SparseDiffHelper.dispatchDiff(mUpdateCallback, diffResult);
    }
    mList = newList;
    newList.addWeakCallback((PagedList<T>) diffSnapshot, mPagedListCallback);
}复制代码

这个理解成给adapter设置集合,理论上设置一次,这里对于多次设置集合做了刷新的比较处理。这里还是用到了RecylerView的 DifffUtil工具.

当然这个库的主要功能是在未滑倒底部时候就进行数据加载,让用户无感知加载,这个处理方法就在getItem(int index)里:

@SuppressWarnings("WeakerAccess")
@Nullable
public T getItem(int index) {
    if (mList == null) {
        throw new IndexOutOfBoundsException("Item count is zero, getItem() call is invalid");
    }

    mList.loadAround(index);
    return mList.get(index);
}复制代码

recyclerView滑动加载到第几个数据时候就会调用adapter的getItem方法,通过判断当前加载的index位置,还有用户设置的预加载位置,从而进行提前加载数据。这里调用的是PagedList的loadAround方法,现在我们去找这个方法的实现在哪:

ContiguousPageList:

@Override
public void loadAround(int index) {
    mLastLoad = index + mPositionOffset;

    int prependItems = mConfig.mPrefetchDistance - (index - mLeadingNullCount);
    int appendItems = index + mConfig.mPrefetchDistance - (mLeadingNullCount + mList.size());

    mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
    if (mPrependItemsRequested > 0) {
        schedulePrepend();
    }

    mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
    if (mAppendItemsRequested > 0) {
        scheduleAppend();
    }
}

@MainThread
private void schedulePrepend() {
    if (mPrependWorkerRunning) {
        return;
    }
    mPrependWorkerRunning = true;

    final int position = mLeadingNullCount + mPositionOffset;
    final T item = mList.get(0);
    mBackgroundThreadExecutor.execute(new Runnable() {
        @Override
        public void run() {
            if (mDetached.get()) {
                return;
            }

            final List<T> data = mDataSource.loadBefore(position, item, mConfig.mPageSize);
            if (data != null) {
                mMainThreadExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        if (mDetached.get()) {
                            return;
                        }
                        prependImpl(data);
                    }
                });
            } else {
                detach();
            }
        }
    });
}

@MainThread
private void prependImpl(List<T> before) {
    final int count = before.size();
    if (count == 0) {
        // Nothing returned from source, stop loading in this direction
        return;
    }

    Collections.reverse(before);
    mList.addAll(0, before);

    final int changedCount = Math.min(mLeadingNullCount, count);
    final int addedCount = count - changedCount;

    if (changedCount != 0) {
        mLeadingNullCount -= changedCount;
    }
    mPositionOffset -= addedCount;
    mNumberPrepended += count;


    // only try to post more work after fully prepended (with offsets / null counts updated)
    mPrependItemsRequested -= count;
    mPrependWorkerRunning = false;
    if (mPrependItemsRequested > 0) {
        // not done prepending, keep going
        schedulePrepend();
    }

    // finally dispatch callbacks, after prepend may have already been scheduled
    for (WeakReference<Callback> weakRef : mCallbacks) {
        Callback callback = weakRef.get();
        if (callback != null) {
            if (changedCount != 0) {
                callback.onChanged(mLeadingNullCount, changedCount);
            }
            if (addedCount != 0) {
                callback.onInserted(0, addedCount);
            }
        }
    }
}

复制代码

由于我们传入的是TiledDataSource,所以这些mDataSource的loadBefore和loadAfter等最终都调用了TiledDataSource的loadRange方法。

到这里为止,好像整体流程已经分析完了,整体流程就看这张图吧:


首先Recyclerview设置PagedListAdater,PagedListAdapter设置对应的PagedList,每次adapter getItme就让PagedList知道用户已经滑到第几个item,PagedList计算这些数量以及设置的各种条件,条件达成就通知DataSource,让其返回数据,数据返回成功,通知PagedListAdapter,让其使用进行高效刷新.

目前该库还是测试阶段,里面还有方法回调都未被用到,本文只是简单分析下源码流程,不过也貌似打开了一些新的思路.

由于本人水平也不高,文中也有不少理解错误之处,也希望能各位指教,一起学习,一起进步.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值