1. 程式人生 > >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原始碼分析
--------------------- -------------------- 

 

作者:低調小一
來源:CSDN
原文:https://blog.csdn.net/wzy_1988/article/details/53445958
版權宣告:本文為博主原創文章,轉載請附上博文連結!