Paging失敗重試機制

需求描述

使用Paging進行分頁請求,失敗後重新發起。本來很簡單的一個問題,拜官方demo PagingWithNetworkSample 所賜,失敗後重試機制用的是private var retry: (() -> Any)? = null,既然官方demo都這麼寫,那我們也照着來一份。
一頓操作後,出問題了。
由於全面向協程coroutines靠攏,rxjava沒有引入,DataSource的三個loadxxx方法中調用Retrofit的call().execute()拿到返回結果,然後交給callback處理。失敗後賦值

retry = {
  loadAfter(params, callback)
}

點擊重試或下拉刷新重新調用該方法:

retry.invoke()

注意:此時在主線程,直接調用retry.invoke()請求接口會拋異常。
有兩個辦法:創建一個子線程執行retry.invoke()不優雅,拋棄;在DataSourceloadxxx方法中使用協程發起請求,一開始我也認爲這種方法是正解,但下拉刷新會有問題(loadInitial方法是異步的,加載過程中會一直阻塞,使用協程刷新動作完成後數據消失,接口返回後纔會添加,失敗就沒數據了,坑),這個時候還有一個下策:就是loadInitial中同步請求,loadAfter方法中異步請求…看着都頭大

解決方案

1、接口請求失敗,callback.onRetryableError(error)
2、重新請求,pagedList.retry()
就這麼簡單

源碼解析

1、尋找retry()

一開始是不知道retry()在哪的,既然要重新調用loadxxx方法,那就看如何重新觸發,
拿loadAfter舉例,先找到loadAfter()本尊:

    public abstract void loadAfter(@NonNull LoadParams<Key> params,
            @NonNull LoadCallback<Key, Value> callback);

接着看什麼地方調用了loadAfter():

    @Override
    final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
            int pageSize, @NonNull Executor mainThreadExecutor,
            @NonNull PageResult.Receiver<Value> receiver) {
        @Nullable Key key = getNextKey();
        if (key != null) {
            loadAfter(new LoadParams<>(key, pageSize),
                    new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
        } else {
            receiver.onPageResult(PageResult.APPEND, PageResult.<Value>getEmptyResult());
        }
    }

同類中的dispatchLoadAfter()方法,接着看dispatchLoadAfter()在哪調用:

    @MainThread
    private void scheduleAppend() {
        mLoadStateManager.setState(LoadType.END, LoadState.LOADING, null);

        final int position = mStorage.getLeadingNullCount()
                + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();

        // safe to access first item here - mStorage can't be empty if we're appending
        final V item = mStorage.getLastLoadedItem();
        mBackgroundThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (isDetached()) {
                    return;
                }
                if (mDataSource.isInvalid()) {
                    detach();
                } else {
                    mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
                            mMainThreadExecutor, mReceiver);
                }
            }
        });
    }

接着找到了scheduleAppend()方法,繼續往上走:

    @Override
    public void retry() {
        super.retry();
        if (mLoadStateManager.getStart() == LoadState.RETRYABLE_ERROR) {
            schedulePrepend();
        }
        if (mLoadStateManager.getEnd() == LoadState.RETRYABLE_ERROR) {
            scheduleAppend();
        }
    }

scheduleAppend()有三個地方調用了,但顯然retry()就是我們需要的

2、retry()觸發條件

找到retry()還沒完,retry()中是有條件限制的:mLoadStateManager.getEnd() == LoadState.RETRYABLE_ERROR,mEnd是mLoadStateManager屬性之一,關鍵在於屬性的賦值:

        void setState(@NonNull LoadType type, @NonNull LoadState state, @Nullable Throwable error) {
            boolean expectError = state == LoadState.RETRYABLE_ERROR || state == LoadState.ERROR;
            boolean hasError = error != null;
            if (expectError != hasError) {
                throw new IllegalArgumentException(
                        "Error states must be accompanied by a throwable, other states must not");
            }

            // deduplicate signals
            switch (type) {
                case REFRESH:
                    if (mRefresh.equals(state) && equalsHelper(mRefreshError, error)) return;
                    mRefresh = state;
                    mRefreshError = error;
                    break;
                case START:
                    if (mStart.equals(state) && equalsHelper(mStartError, error)) return;
                    mStart = state;
                    mStartError = error;
                    break;
                case END:
                    if (mEnd.equals(state) && equalsHelper(mEndError, error)) return;
                    mEnd = state;
                    mEndError = error;
                    break;
            }
            onStateChanged(type, state, error);
        }

setState()第一個參數是LoadType,目前我們只關注END這種類型,第二個參數是LoadState,從retry()方法來看,我們要注意的是LoadState.RETRYABLE_ERROR類型,第三個參數是異常,不能爲空,根據這三個線索,滿足條件的只有ContiguousPagedList中的onPageError

        @Override
        public void onPageError(@PageResult.ResultType int resultType,
                @NonNull Throwable error, boolean retryable) {
            LoadState errorState = retryable ? LoadState.RETRYABLE_ERROR : LoadState.ERROR;

            if (resultType == PageResult.PREPEND) {
                mLoadStateManager.setState(LoadType.START, errorState, error);
            } else if (resultType == PageResult.APPEND) {
                mLoadStateManager.setState(LoadType.END, errorState, error);
            } else {
                // TODO: pass init signal through to *previous* list
                throw new IllegalStateException("TODO");
            }
        }

接着找到了dispatchOnCurrentThread

        void dispatchOnCurrentThread(@Nullable PageResult<T> result,
                @Nullable Throwable error, boolean retryable) {
            if (result != null) {
                mReceiver.onPageResult(mResultType, result);
            } else {
                mReceiver.onPageError(mResultType, error, retryable);
            }
        }

繼續:

        private void dispatchToReceiver(final @Nullable PageResult<T> result,
                final @Nullable Throwable error, final boolean retryable) {
            Executor executor;
            synchronized (mSignalLock) {
                if (mHasSignalled) {
                    throw new IllegalStateException(
                            "callback.onResult/onError already called, cannot call again.");
                }
                mHasSignalled = true;
                executor = mPostExecutor;
            }

            if (executor != null) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        dispatchOnCurrentThread(result, error, retryable);
                    }
                });
            } else {
                dispatchOnCurrentThread(result, error, retryable);
            }
        }

dispatchToReceiver()有兩個地方調用,但error不能爲空,retryable=true,所以是:

        void dispatchErrorToReceiver(@NonNull Throwable error, boolean retryable) {
            dispatchToReceiver(null, error, retryable);
        }

dispatchErrorToReceiver()LoadCallbackHelper中的方法,每個類型的DataSource都有調用,我們隨便找一個:

        @Override
        public void onRetryableError(@NonNull Throwable error) {
            mCallbackHelper.dispatchErrorToReceiver(error, true);
        }

onRetryableError()LoadCallback的實現方法,也就是DataSource的三個loadxxx方法中的LoadCallback,所以調用callback.onRetryableError(error)便可配置retry()觸發條件。

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