Android组件Paging代码的简单分析

入口

从使用角度分析,从LivePagedListBuilder开始
创建LivePagedListBuilder代码示例:

        val sourceFactory = FeedListDataSourceFactory(NETWORK_IO)
        val livePagedList = LivePagedListBuilder(sourceFactory, 10)
                .setBoundaryCallback(FeedlistBoundaryCallback())
                .setFetchExecutor(NETWORK_IO)
                .build()

--> 进入build()方法

@NonNull
    public LiveData> build() {
        return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
                ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
    }

创建并返回 ComputableLiveData的.getLiveData()调用

private static  LiveData> create(...) {
    return new ComputableLiveData>(fetchExecutor) {
        ...
        @Override
        protected PagedList compute() {
            ...
            return mList;
        }
    }.getLiveData();

具体看一下其返回的LiveData定义: 也就是mLiveData

 public ComputableLiveData(@NonNull Executor executor) {
        mExecutor = executor;
        mLiveData = new LiveData() {
            @Override
            protected void onActive() {
                mExecutor.execute(mRefreshRunnable);
            }
        };
    }

看到, 当有observer关注这个实例时, 会触发执行mRefreshRunnable.
而这个mRefreshRunnable中的逻辑非常值得关注, 这里列出完整代码:

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

使用mComputing变量来实现一个简单独占锁, 并且不block其他线程.
如果进行了计算(computed为true ) 就更新mLiveData中的值. 这个mLiveData就是最前面通过LivePagedListBuilder的build()方法返回的那个liveData实例.
所以可以先简单认为执行一次mRefreshRunnable, 就会更新一次liveData的值, ui层的observer就会得到更新的通知.

到这里初始化看似就结束了. 但还漏了一步, 我们自定义的DataSource会自动执行loadInitial()方法. 这是怎么执行流程呢?
下面分析下.
触发loadInitial()的入口是UI层对liveData的监听, 示例如下:

     model.topicList.observe(this, Observer> { topics ->
            Log.d(TAG, "observe. $topics")
            if (topics != null && topics.size > 0) {
                progressBar.visibility = View.INVISIBLE
            }
            adapter.submitList(topics)
        })

前面说过, 当进行observe监听设置的时候, 会触发mRefreshRunnable执行.
而在mRefreshRunnable执行逻辑中又会执行compute(); 看看这个方法的实现:

      return new ComputableLiveData>(fetchExecutor) {
            @Nullable
            private PagedList mList;
            @Nullable
            private DataSource mDataSource;

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

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

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

                    mDataSource = dataSourceFactory.create();
                    mDataSource.addInvalidatedCallback(mCallback);

                    mList = new PagedList.Builder<>(mDataSource, config)
                            .setNotifyExecutor(notifyExecutor)
                            .setFetchExecutor(fetchExecutor)
                            .setBoundaryCallback(boundaryCallback)
                            .setInitialKey(initializeKey)
                            .build();
                } while (mList.isDetached());
                return mList;
            }
        }.getLiveData();

关注其中的compute()方法, 首先是生成mDataSource对象, 然后再生成mList并返回, 这个返回的mList就是返回给UI层监听器的具体数据.
所以重点就是mList的创建过程了. 又是一个眼熟的build()构建方法.
PageList.java

@NonNull
    private static  PagedList create(@NonNull DataSource dataSource,
            @NonNull Executor notifyExecutor,
            @NonNull Executor fetchExecutor,
            @Nullable BoundaryCallback boundaryCallback,
            @NonNull Config config,
            @Nullable K key) {
        if (dataSource.isContiguous() || !config.enablePlaceholders) {
            int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
            if (!dataSource.isContiguous()) {
                //noinspection unchecked
                dataSource = (DataSource) ((PositionalDataSource) dataSource)
                        .wrapAsContiguousWithoutPlaceholders();
                if (key != null) {
                    lastLoad = (Integer) key;
                }
            }
            ContiguousDataSource contigDataSource = (ContiguousDataSource) dataSource;
            return new ContiguousPagedList<>(contigDataSource,
                    notifyExecutor,
                    fetchExecutor,
                    boundaryCallback,
                    config,
                    key,
                    lastLoad);
        } else {
            return new TiledPagedList<>((PositionalDataSource) dataSource,
                    notifyExecutor,
                    fetchExecutor,
                    boundaryCallback,
                    config,
                    (key != null) ? (Integer) key : 0);
        }
    }

看到这里, 有个问题: ContiguousPagedList和TiledPagedList各自是什么语义?
不知道. 简单说的话, ItemKeyedDataSource和PageKeyedDataSource这两种datasource对应的是ContiguousPagedList
而 PositionalDataSource对应的是TiledPagedList.
反推过来, 自己理解吧.

我们这里分析的是ContiguousPagedList. 所以进入其构造方法.

ContiguousPagedList(
            @NonNull ContiguousDataSource dataSource,
            @NonNull Executor mainThreadExecutor,
            @NonNull Executor backgroundThreadExecutor,
            @Nullable BoundaryCallback boundaryCallback,
            @NonNull Config config,
            final @Nullable K key,
            int lastLoad) {
        super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor,
                boundaryCallback, config);
        mDataSource = dataSource;
        mLastLoad = lastLoad;

        if (mDataSource.isInvalid()) {
            detach();
        } else {
            mDataSource.dispatchLoadInitial(key,
                    mConfig.initialLoadSizeHint,
                    mConfig.pageSize,
                    mConfig.enablePlaceholders,
                    mMainThreadExecutor,
                    mReceiver);
        }
    }

在这里面我们就看到了调用 mDataSource.dispatchLoadInitial(). 再下一步就是loadInitial()了.
至于这句判断:

       if (mDataSource.isInvalid()) {
            detach();
        }

想不明白, 什么时候会出现这种场景. 先不管吧.
进入PageKeyedDataSource.java 看看其dispatchLoadInitial()实现

@Override
    final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
            boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
            @NonNull PageResult.Receiver receiver) {
        LoadInitialCallbackImpl callback =
                new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
        loadInitial(new LoadInitialParams(initialLoadSize, enablePlaceholders), callback);

        // If initialLoad's callback is not called within the body, we force any following calls
        // to post to the UI thread. This constructor may be run on a background thread, but
        // after constructor, mutation must happen on UI thread.
        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
    }

这里这句注释很重要, 但是我还是没有理解. 谁能给说说, 他到底是什么目的设计成这个样子. 这不是bug吗?

一定要在前面的oadInitial(new LoadInitialParams(initialLoadSize, enablePlaceholders), callback);调用中进行同步加载, 异步的话你会倒霉. 也就是千万不要再在其中使用rxjava那种切换线程的方式去加载数据了, 直接在当前线程加载并返回给callback就好了. 此处浪费我将近5个小时调试. 之所以写这篇分析, 就是为了记录这个问题.

好了 流程先分析到这里. 以后有精力了再写.

还有一个问题:

如果我设置了BoundaryCallback, 那什么情况下才会触发?
 val livePagedList = LivePagedListBuilder(sourceFactory, 10)
                .setBoundaryCallback(FeedlistBoundaryCallback())
                .setFetchExecutor(NETWORK_IO)
                .build()

答: 对于通过loadAfter加载数据的模式, 只有在loadAfter返回0条数据, 并且滑到底部时才认为是到达边界了, 这两个条件必须同时满足. 也就是说, 单纯的滑动到底部, 并不会触发BoundaryCallback的onItemAtEndLoaded()回调.

你可能感兴趣的:(Android组件Paging代码的简单分析)