Android Paging数据刷新及原理解析

Paging

刷新数据以及解析

从刷新讲起

流程

Paging的确很好用,省了很多步骤,但跟以往简单的刷新不同的是,Paging比较麻烦,不能直接清空Adapter里面的数据了和直接请求第一页的数据。以前清除列表,再重新请求第一页数据就可以了。那么Paging如何刷新呢?大致上也和以前用法一样,无外乎就是清除数据并加载新数据

清除Adapter里面的数据

submitList()通过传入null值,如下源码:

public void submitList(final PagedList<T> pagedList) {
    ...

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

    if (pagedList == null) {
        int removedCount = getItemCount();
        if (mPagedList != null) {
            mPagedList.removeWeakCallback(mPagedListCallback);
            mPagedList = null;
        } else if (mSnapshot != null) {
            mSnapshot = null;
        }
        // dispatch update callback after updating mPagedList/mSnapshot
        mUpdateCallback.onRemoved(0, removedCount);
        if (mListener != null) {
            mListener.onCurrentListChanged(null);
        }
        return;
    }

    ...
}

也就是在传入值为null的情况下,最终会执行

mUpdateCallback.onRemoved(0, removedCount);

ListUpdateCallback是一个接口,其实现类如下:

/**
 * ListUpdateCallback that dispatches update events to the given adapter.
 *
 * @see DiffUtil.DiffResult#dispatchUpdatesTo(RecyclerView.Adapter)
 */
public final class AdapterListUpdateCallback implements ListUpdateCallback {
    @NonNull
    private final RecyclerView.Adapter mAdapter;

    /**
     * Creates an AdapterListUpdateCallback that will dispatch update events to the given adapter.
     *
     * @param adapter The Adapter to send updates to.
     */
    public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) {
        mAdapter = adapter;
    }

    /** {@inheritDoc} */
    @Override
    public void onInserted(int position, int count) {
        mAdapter.notifyItemRangeInserted(position, count);
    }

    /** {@inheritDoc} */
    @Override
    public void onRemoved(int position, int count) {
        mAdapter.notifyItemRangeRemoved(position, count);
    }

    /** {@inheritDoc} */
    @Override
    public void onMoved(int fromPosition, int toPosition) {
        mAdapter.notifyItemMoved(fromPosition, toPosition);
    }

    /** {@inheritDoc} */
    @Override
    public void onChanged(int position, int count, Object payload) {
        mAdapter.notifyItemRangeChanged(position, count, payload);
    }
}

也就是onRemoved(0, removedCount)还是调用了RecyclerView.AdapternotifyItemRangeRemoved(),因此,如果在submitList()传入null值,最终会让Adapter移除从位置0开始的getItemCount()个数据,即删除全部数据

请求第一页的数据

那么还有一个问题,就是加载第一页数据。Paging并没有根据页数请求数据的方法。即使接口是有提供这个方法,那么只能看DataSourceloadInitial()是在什么情况下被调用的了

在本项目中,DataSource继承自PageKeyedDataSource,查看在该类中loadInitial()被调用情况

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

PageKeyedDataSource中只被调用一次,即dispatchLoadInitial()dispatchLoadInitial()又是在ContiguousPagedList中的构造方法中被调用,即创建ContiguousPagedList时就开始请求第一页的数据

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 = dataSource;
    mLastLoad = lastLoad;

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

继续循着调用链往上找,就能看到是在PagedListcreate()中开始去创建ContiguousPagedList实例

@NonNull
private 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) {
    if (dataSource.isContiguous() || !config.enablePlaceholders) {
        int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
        if (!dataSource.isContiguous()) {
            //noinspection unchecked
            dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
                .wrapAsContiguousWithoutPlaceholders();
            if (key != null) {
                lastLoad = (int) key;
            }
        }
        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);
    }
}

在代码中有两个条件,就能去创建ContiguousPagedList实例了

if (dataSource.isContiguous() || !config.enablePlaceholders) {
    
    return new ContiguousPagedList<>(contigDataSource,
                                     notifyExecutor,
                                     fetchExecutor,
                                     boundaryCallback,
                                     config,
                                     key,
                                     lastLoad);
}

isContiguous()DataSource是抽象方法。看下DataSourceHierarchy,如图:

image

isContiguous()从字面上判断就是“是否为连续的”,而且在ContiguousDataSource类中就已经实现,他是永远返回true

@Override
boolean isContiguous() {
    return true;
}

走到这一步就已经可以判断可以往下执行了,因为是关系,一个为true就可以了,但是另一个条件:!config.enablePlaceholders,在创建GankDataViewModel实例中就已经通过setEnablePlaceholders(false)决定好了,它的值是false

public GankDataViewModel() {
    init();
}

private void init() {
    mExecutor = Executors.newFixedThreadPool(3);
    mFactory = new GankDataSourceFactory();
    mPagedListConfig = (new PagedList.Config.Builder())
        .setEnablePlaceholders(false)//配置是否启动PlaceHolders
        .setInitialLoadSizeHint(20)//初始化加载的数量
        .setPageSize(10)//配置分页加载的数量
        .build();
    ...
}

回到之前所讲的PagedListcreate()这一步。create()是在PagedList的build()方法中调用,PagedList采用建造者模式,创建一个PagedList实例,PagedList也是一个集合类。此时重心转移到了PagedList初始化的时机上面。继续跟进,如下:

//LivePagedListBuilder类
@AnyThread
@NonNull
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();
            }
        };

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

嗯,是创建了LiveData对象,但是什么时候调用compute()呢?毕竟是在这个方法中去创建PagedList,进而去调用loadInitial()请求数据

LivePagedListBuilder.create()实际返回的是一个ComputableLiveData对象,这个类并不是LiveData的子类,而是内部有一个成员变量是LiveData类型的

public abstract class ComputableLiveData<T> {
    ...
    private final Executor mExecutor;
    private final LiveData<T> mLiveData;
    
    /**
     * Creates a computable live data that computes values on the arch IO thread executor.
     */
    @SuppressWarnings("WeakerAccess")
    public ComputableLiveData() {
        this(ArchTaskExecutor.getIOThreadExecutor());
    }

    /**
     *
     * Creates a computable live data that computes values on the specified executor.
     *
     * @param executor Executor that is used to compute new LiveData values.
     */
    @SuppressWarnings("WeakerAccess")
    public ComputableLiveData(@NonNull Executor executor) {
        mExecutor = executor;
        mLiveData = new LiveData<T>() {
            @Override
            protected void onActive() {
                mExecutor.execute(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());
        }
    };
    ... 
}

在初始化ComputableLiveData时也会创建LiveData对象,并重写了onActive(),使其在active状态下执行mRefreshRunnable任务,从而调用compute()和发出事件postValue(),这个事件在这里可以认为是刷新数据

已经创建了ComputableLiveData,也知道ComputableLiveData什么时候会去调用compute()去创建PagedList了,那么是谁调用LivePagedListBuilder.create()?是由同类的build()方法调用

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

此时,答案终于明显了,在自定义ViewModel(GankDataViewModel)中的构造方法中也创建了LiveData对象监听列表数据变化,并submitListAdapter

//GankDataViewModel
public GankDataViewModel() {
    init();
}

private void init() {
    ...

    mPagedListLiveData = (new LivePagedListBuilder(mFactory, mPagedListConfig))
        .setFetchExecutor(mExecutor)
        .build();
}

梳理

感觉逆着调用链去找好像漏了点,在此梳理下。不过为了防止重复,会简略很多

项目中通过LiveData监听数据变化,而LiveData是在ViewModel中创建的,这时候就需要创建ViewModel了,当然在Activity/Fragment中用来监听的LiveData和发送通知的LiveData需要是同一个

在创建ViewModel时,也会创建PagedList.Config对象和LiveData<PagedList<>>对象。创建LiveData<PagedList<>>是通过LivePagedListBuilder对象来得到的。通过链式调用执行到build()后,就返回LiveData<PagedList<>>对象了

//自定义ViewModel
mPagedListLiveData = (new LivePagedListBuilder(mFactory, mPagedListConfig))
        .setFetchExecutor(mExecutor)
        .build();

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

build()调用了其create()方法,该方法返回ComputableLiveData<PagedList<>>对象,并且在这个对象中,通过调用compute()就通过传入的dataSourceFactory调用重写的create()创建DataSource,及创建了PagedList对象

ComputableLiveData对象在创建时就会在传入的Activity等生命周期对象处理active状态下就会去调用compute(),由此创建了PagedList对象,当然返回的是PagedList子类ContiguousPagedList的实例,在其构造器中,将会调用此前创建的DataSourcedispatchLoadInitial(),进而调用loadInitial(),开始请求第一页的数据(PageKeyedDataSource

总结

已经知道什么时候Paging去请求新数据了,那么如何刷新呢?重新创建LiveData<PagedList<>>就可以了

public LiveData<PagedList<FuliDataBean.ResultsBean>> getRefreshLiveData(){
    mPagedListLiveData = (new LivePagedListBuilder(mFactory, mPagedListConfig))
        .setFetchExecutor(mExecutor)
        .build();
    return mPagedListLiveData;
}

当然还有其他事情要做好

//MainActivity
private void loadData() {
    mGankFuliDataViewModel = GankRepository.getGankFuliDataViewModel(this);
    mGankFuliDataViewModel.getGankFuliLiveData().observe(this, new Observer<PagedList<FuliDataBean.ResultsBean>>() {
        @Override
        public void onChanged(@Nullable PagedList<FuliDataBean.ResultsBean> resultsBeans) {

            mRefreshLayout.setRefreshing(false);
            mAdapter.submitList(resultsBeans);
            LogUtils.e("ExerciseClient_net",mAdapter.getItemCount()+"");
        }
    });
}

@Override
public void onRefresh() {
    mAdapter.submitList(null);
    mGankFuliDataViewModel.getRefreshLiveData().observe(this, new Observer<PagedList<FuliDataBean.ResultsBean>>() {
        @Override
        public void onChanged(@Nullable PagedList<FuliDataBean.ResultsBean> listBeans) {
            mRefreshLayout.setRefreshing(false);
            mAdapter.submitList(listBeans);
        }
    });
}

//GankDataViewModel
public LiveData<PagedList<FuliDataBean.ResultsBean>> getGankFuliLiveData() {
    return mPagedListLiveData;
}

public LiveData<PagedList<FuliDataBean.ResultsBean>> getRefreshLiveData(){
    mPagedListLiveData = (new LivePagedListBuilder(mFactory, mPagedListConfig))
        .setFetchExecutor(mExecutor)
        .build();
    return mPagedListLiveData;
}

注意:最好用同一个LiveData变量去引用

项目地址为ExerciseClient

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章