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()觸發條件。