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

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