Jetpack-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()不优雅,抛弃;在DataSource
的loadxxx
方法中使用协程发起请求,一开始我也认为这种方法是正解,但下拉刷新会有问题(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()触发条件。