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()触发条件。

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