目錄
一、核心類
二忠荞、重試策略
參考資料
本文是建立在對Volley框架的工作流程有一定基礎了解的情況下繼續(xù)深入的,這里順便貼上我寫的上篇文章《重溫Volley源碼(一):工作流程》 ,歡迎閱讀指正夫嗓。
一桐玻、核心類
RetryPolicy:Volley定義的請求重試策略接口
public interface RetryPolicy {
/**當前請求的超時時間
* Returns the current timeout (used for logging).
*/
public int getCurrentTimeout();
/**當前請求重試的次數(shù)
* Returns the current retry count (used for logging).
*/
public int getCurrentRetryCount();
/**在請求異常時調用此方法
* Prepares for the next retry by applying a backoff to the timeout.
* @param error The error code of the last attempt.
* @throws VolleyError In the event that the retry could not be performed (for example if we
* ran out of attempts), the passed in error is thrown.
*/
public void retry(VolleyError error) throws VolleyError;
}
DefaultRetryPolicy:RetryPolicy的實現(xiàn)子類
public class DefaultRetryPolicy implements RetryPolicy {
/** The current timeout in milliseconds. 當前超時時間*/
private int mCurrentTimeoutMs;
/** The current retry count. 當前重試次數(shù)*/
private int mCurrentRetryCount;
/** The maximum number of attempts. 最大重試次數(shù)*/
private final int mMaxNumRetries;
/** The backoff multiplier for the policy. 超時時間的乘積因子*/
private final float mBackoffMultiplier;
/** The default socket timeout in milliseconds 默認超時時間*/
public static final int DEFAULT_TIMEOUT_MS = 2500;
/** The default number of retries 默認的重試次數(shù)*/
public static final int DEFAULT_MAX_RETRIES = 0;
/** The default backoff multiplier 默認超時時間的乘積因子*/
/**
* 以默認超時時間為2.5s為例
* DEFAULT_BACKOFF_MULT = 1f, 則每次HttpUrlConnection設置的超時時間都是2.5s*1f*mCurrentRetryCount
* DEFAULT_BACKOFF_MULT = 2f, 則第二次超時時間為:2.5s+2.5s*2=7.5s,第三次超時時間為:7.5s+7.5s*2=22.5s
*/
public static final float DEFAULT_BACKOFF_MULT = 1f;
/**
* Constructs a new retry policy using the default timeouts.
*/
public DefaultRetryPolicy() {
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
/**
* Constructs a new retry policy.
* @param initialTimeoutMs The initial timeout for the policy.
* @param maxNumRetries The maximum number of retries.
* @param backoffMultiplier Backoff multiplier for the policy.
*/
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
/**
* Returns the current timeout.
*/
@Override
public int getCurrentTimeout() {
return mCurrentTimeoutMs;
}
/**
* Returns the current retry count.
*/
@Override
public int getCurrentRetryCount() {
return mCurrentRetryCount;
}
/**
* Returns the backoff multiplier for the policy.
*/
public float getBackoffMultiplier() {
return mBackoffMultiplier;
}
/**
* Prepares for the next retry by applying a backoff to the timeout.
* @param error The error code of the last attempt.
*/
@Override
public void retry(VolleyError error) throws VolleyError {
//當前重試次數(shù)++
mCurrentRetryCount++;
//當前超時時間計算
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
//判斷是否還有剩余次數(shù)篙挽,如果沒有則拋出VolleyError異常
if (!hasAttemptRemaining()) {
throw error;
}
}
/**
* 判斷當前Request的重試次數(shù)是否超過最大重試次數(shù)
* Returns true if this policy has attempts remaining, false otherwise.
*/
protected boolean hasAttemptRemaining() {
return mCurrentRetryCount <= mMaxNumRetries;
}
}
二、重試策略
在深入源碼閱讀你會發(fā)現(xiàn)镊靴,retry方法會拋出VolleyError的異常铣卡,但該方法內部并不是重新發(fā)起了網(wǎng)絡請求,而是變更重試策略的屬性偏竟,如超時時間和重試次數(shù)煮落,當超過重試策略設定的限定就會拋異常,這個可以在DefaultRetryPolicy里得到驗證踊谋。那么它究竟是如何做到重試的呢蝉仇,我們可以跟蹤源碼 retry 方法被調用到的地方,來到了BasicNetwork的attemptRetryOnException方法:
public class BasicNetwork implements Network {
......
/**
* 嘗試對一個請求進行重試策略殖蚕,
*/
private static void attemptRetryOnException(String logPrefix, Request<?> request,
VolleyError exception) throws VolleyError {
RetryPolicy retryPolicy = request.getRetryPolicy(); //獲取該請求的重試策略
int oldTimeout = request.getTimeoutMs(); //獲取該請求的超時時間
try {
retryPolicy.retry(exception); //內部實現(xiàn)重試次數(shù)轿衔、超時時間的變更,如果重試次數(shù)超過最大限定次數(shù)睦疫,該方法拋出異常
} catch (VolleyError e) {
//當超過最大重試次數(shù)害驹,捕獲到異常,更改該請求的標記
request.addMarker(
String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
//當仍然可以進行重試的時候蛤育,不會執(zhí)行到catch語句宛官,但是當執(zhí)行到catch語句的時候,表示已經(jīng)不能進行重試了瓦糕,就拋出異常 中斷while(true)循環(huán)
throw e;
}
//給請求添加標記底洗,請求了多少次
request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
}
}
繼續(xù)跟蹤attemptRetryOnException方法被調用的地方,來到了BasicNetwork的performRequest方法:
public class BasicNetwork implements Network {
......
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
......
try {
......
//如果發(fā)生了超時咕娄、認證失敗等錯誤枷恕,進行重試操作,直到成功谭胚。若attemptRetryOnException再拋出異常則結束
//當catch后沒有執(zhí)行上面的return徐块,而當前又是一個while(true)循環(huán)未玻,可以保證下面的請求重試的執(zhí)行,是利用循環(huán)進行請求重試胡控,請求重試策略只是記錄重試的次數(shù)扳剿、超時時間等內容
return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
} catch (SocketTimeoutException e) {
//1.嘗試進行請求重試
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException e) {
//2.嘗試進行請求重試
attemptRetryOnException("connection", request, new TimeoutError());
} catch (MalformedURLException e) {
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
int statusCode = 0;
NetworkResponse networkResponse = null;
if (httpResponse != null) {
statusCode = httpResponse.getStatusLine().getStatusCode();
} else {
throw new NoConnectionError(e);
}
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
} else {
VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
}
if (responseContents != null) {
networkResponse = new NetworkResponse(statusCode, responseContents,
responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
statusCode == HttpStatus.SC_FORBIDDEN) {
//3.嘗試進行請求重試
attemptRetryOnException("auth",
request, new AuthFailureError(networkResponse));
} else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
//4.嘗試進行請求重試
attemptRetryOnException("redirect",
request, new RedirectError(networkResponse));
} else {
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);
}
} else {
throw new NetworkError(e);
}
}
}
}
}
performRequest這個方法名是否很眼熟?是的昼激,它曾被我在上一篇文章中簡單提到過庇绽,還記得在NetworkDispatcher的run方法中,mNetWork對象(即BasicNetwork)會調用performRequest來執(zhí)行請求橙困,在該方法內部又是一個while(true)循環(huán)
在這個方法里attemptRetryOnException總共被調用了四次:
- 發(fā)生SocketTimeoutException時(Socket通信超時瞧掺,即從服務端讀取數(shù)據(jù)時超時)
- 發(fā)生ConnectTimeoutException時(請求超時,即連接HTTP服務端超時或者等待HttpConnectionManager返回可用連接超時)
- 發(fā)生IOException凡傅,相應的狀態(tài)碼401/403(授權未通過)時
- 發(fā)生IOException辟狈,相應的狀態(tài)碼301/302(URL發(fā)生轉移)時
現(xiàn)在我們歸納一下,首先假設我們設置了請求重試的策略(Volley默認最大請求重試次數(shù)為0夏跷,即不重試)哼转,其次BasicNetwork的performRequest方法的外面其實是個while循環(huán),假如在網(wǎng)絡請求過程中發(fā)生了異常槽华,如超時壹蔓、認證未通過等上述四個被調用的異常,catch這個異常的代碼會看看是否可以重試猫态,如果可以重試佣蓉,就會把這個異常吞噬掉,然后進入下一次循環(huán)亲雪,否則超過重試次數(shù)勇凭,attemptRetryOnException內部再拋出異常,此時交由上一層代碼去處理匆光,并退出while循環(huán)。
可能還有個疑問酿联,上面說的交由上一層代碼去處理是到底是怎么處理并退出while循環(huán)的终息?這里因為BasicNetwork的performRequest方法并沒有捕獲VolleyError異常,因此沒有被try&catch住的異常會繼續(xù)往外拋出贞让,這里我們回過頭來看看NetworkDispatcher的run方法里頭:
public class NetworkDispatcher extends Thread {
......
@Override
public void run() {
......
while (true) {
......
try {
......
// Perform the network request. 真正執(zhí)行網(wǎng)絡請求的地方周崭,BasicNetwork超時拋出的VolleyError最終會拋出到這里
NetworkResponse networkResponse = mNetwork.performRequest(request);
......
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
// 捕獲VolleyError異常,通過主線程Handler回調用戶設置的ErrorListener中的onErrorResponse回調方法
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}
}
Request無法繼續(xù)重試后拋出的VolleyError異常,會被NetworkDispatcher捕獲喳张,然后利用Delivery去回調用戶設置的ErrorListener续镇。