Volley超時重試機制詳解
Volley超時重試機制
基礎用法
Volley為開發者提供了可配置的超時重試機制,我們在使用時只需要為我們的Request設定自定義的RetryPolicy即可.
參考設定程式碼如下:
int DEFAULT_TIMEOUT_MS = 10000;
int DEFAULT_MAX_RETRIES = 3;
StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
@Override
public void onResponse(String s) {
LogUtil.i(TAG, "res=" + s);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
LogUtil.e(TAG, volleyError.toString());
}
});
// 設定Volley超時重試策略
stringRequest.setRetryPolicy(new DefaultRetryPolicy(
DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
RequestQueue requestQueue = VolleyManager.getInstance(context).getRequestQueue();
requestQueue.add(stringRequest);
基礎知識
在講解Volley的超時重試原理之前,需要先普及一下跟超時重試相關的異常.
org.apache.http.conn.ConnectTimeoutException
A timeout while connecting to an HTTP server or waiting for an available connection from an HttpConnectionManager.
連線HTTP服務端超時或者等待HttpConnectionManager返回可用連線超時,俗稱請求超時.
java.net.SocketTimeoutException
Signals that a timeout has occurred on a socket read or accept.
Socket通訊超時,即從服務端讀取資料時超時,俗稱響應超時.
Volley就是通過捕捉這兩個異常來進行超時重試的.
Volley捕捉超時異常
看過之前Volley原始碼分析的同學,應該知道Volley中是通過BasicNetwork去執行網路請求的.相關原始碼如下:
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
// 記錄請求開始的時間,便於進行超時重試
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = Collections.emptyMap();
try {
// ......省略部分新增HTTP-HEADER的程式碼
// 呼叫HurlStack的performRequest方法執行網路請求, 並將請求結果存入httpResponse變數中
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// ......省略部分對返回值處理的程式碼
return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
} catch (SocketTimeoutException e) {
// 捕捉響應超時
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException E) {
// 捕捉請求超時
attemptRetryOnException("connection", request, new TimeoutError());
} catch (IOException e) {
// 省略對IOException的異常處理,不考慮服務端錯誤的重試機制
}
}
}
private void attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception) throws VolleyError{
RetryPolicy retryPolicy = request.getRetryPolicy();
int oldTimeout = request.getTimeoutMs();
retryPolicy.retry(exception);
Log.e("Volley", String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
}
因為BasicNetwork類中是用java while(true)
包裹連線請求,因此如果捕捉到程式丟擲SocketTimeoutException或者ConnectTimeoutException,並不會跳出退出迴圈的操作,而是進入到attemptRetryOnException方法.
如果attemptRetryOnException方法中沒有丟擲VolleyError異常,最終程式還是可以再次進入while迴圈,從而完成超時重試機制.
接下來,我們看一下RetryPolicy是如何判斷是否需要進行超時重試,並且是如何停止超時重試的.
RetryPolicy.java
RetryPolicy是一個介面定義,中文註釋的原始碼如下:
/**
* Request請求的重試策略類.
*/
@SuppressWarnings("unused")
public interface RetryPolicy {
/**
* 獲取當前請求的超時時間
*/
int getCurrentTimeout();
/**
* 獲取當前請求的重試次數
*/
int getCurrentRetryCount();
/**
* 實現類需要重點實現的方法,用於判斷當前Request是否還需要再進行重試操作
*/
void retry(VolleyError error) throws VolleyError;
}
RetryPolicy只是Volley定義的Request請求重試策略介面,同時也提供了DefaultRetryPolicy實現類來幫助開發者來快速實現自定製的請求重試功能.
DefaultRetryPolicy.java
中文註釋的原始碼如下:
public class DefaultRetryPolicy implements RetryPolicy {
/**
* Request當前超時時間
*/
private int mCurrentTimeoutMs;
/**
* Request當前重試次數
*/
private int mCurrentRetryCount;
/**
* Request最多重試次數
*/
private final int mMaxNumRetries;
/**
* Request超時時間乘積因子
*/
private final float mBackoffMultiplier;
/**
* Volley預設的超時時間(2.5s)
*/
public static final int DEFAULT_TIMEOUT_MS = 2500;
/**
* Volley預設的重試次數(0次,不進行請求重試)
*/
public static final int DEFAULT_MAX_RETRIES = 0;
/**
* 預設超時時間的乘積因子.
* 以預設超時時間為2.5s為例:
* 1. DEFAULT_BACKOFF_MULT = 1f, 則每次HttpUrlConnection設定的超時時間都是2.5s*1f*mCurrentRetryCount.
* 2. 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;
/**
* Request的預設重試策略建構函式
* 超時時間:2500ms
* 重試次數:0次
* 超時時間因子:1f
*/
public DefaultRetryPolicy() {
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
/**
* 開發者自定製Request重試策略建構函式
*
* @param initialTimeoutMs 超時時間
* @param maxNumRetries 最大重試次數
* @param backoffMultiplier 超時時間乘積因子
*/
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
@Override
public int getCurrentTimeout() {
return mCurrentTimeoutMs;
}
@Override
public int getCurrentRetryCount() {
return mCurrentRetryCount;
}
@Override
public void retry(VolleyError error) throws VolleyError {
// 新增重試次數
mCurrentRetryCount ++;
// 累加超時時間
mCurrentTimeoutMs += mCurrentTimeoutMs * mBackoffMultiplier;
// 判斷是否還有剩餘次數,如果沒有,則丟擲VolleyError異常
if (!hasAttemptRemaining()) {
throw error;
}
}
/**
* 判斷當前Request的重試次數是否超過最大重試次數
*/
private boolean hasAttemptRemaining() {
return mCurrentTimeoutMs <= mMaxNumRetries;
}
}
本文開始時就講過Request如何設定自定製的RetryPolicy,結合中文註釋的DefaultRetryPolicy原始碼,相信大家很容易就能理解自定製RetryPolicy的引數含義和作用.
目前可能大家還有一個疑問,為什麼DefaultRetryPolicy的retry方法丟擲VolleyError異常,就能退出BasicNetwork類performRequest的while(true)迴圈呢?
這是因為BasicNetwork並沒有捕獲VolleyError異常,因此沒有被try&catch住的異常會終止當前程式的執行,繼續往外丟擲,這時候就回到NetworkDispatcher類,相關原始碼如下:
@Override
public void run() {
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// 使用BlockingQueue實現了生產者-消費者模型.
// 消費者是該排程執行緒.
// 生產者是request網路請求.
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
if (request.isCanceled()) {
continue;
}
addTrafficStatsTag(request);
// 真正執行網路請求的地方.(BasicNetwork由於超時丟擲的VolleyError會丟擲到這裡)
NetworkResponse networkResponse = mNetwork.performRequest(request);
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
// 捕獲VolleyError異常,通過主執行緒Handler回撥使用者設定的ErrorListener中的onErrorResponse回撥方法.
volleyError.printStackTrace();
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}
從上述原始碼中可以看出,Request無法繼續重試後丟擲的VolleyError異常,會被NetworkDispatcher捕獲,然後通過Delivery去回撥使用者設定的ErrorListener.
小結
至此,Volley的超時重試機制就分析完了,在本文末尾給大家推薦一下Volley預設重試策略的引數.
預設超時時間:Volley的預設2500ms確實有點短,大家可以設定成HttpClient的預設超時時間,也就是10000ms.
預設重試次數:建議為3次,可根據業務自行調整.
預設超時時間因子:建議採用DefaultRetryPolicy的預設值1f即可,不然曲線增長太快會造成頁面長時間的等待.
更詳細的Volley原始碼分析,大家可以參考我的GitHub專案:Volley原始碼分析