1. 程式人生 > >Volley請求的重發機制

Volley請求的重發機制

暮鼓集    行走集

原作於2017年01月02日

在傳送網路請求時,常常會遇到超時、迴應錯誤等情形,這時便需要考慮是否重發請求。Volley提供了一套機制,用於檢查問題及重發請求,這個機制的核心是RetryPolicy介面。

RetryPolicy

public interface RetryPolicy {

    /**
     * Returns the current timeout (used for logging).
     */
    public int getCurrentTimeout();

    /**
     * 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;
}

方法getCurrentTimeout()返回請求超時的值,以毫秒為單位。Volley的Network底層執行請求操作時,會將這個值用作請求的超時時間,當超時發生時,就會觸發RetryPolicy。

方法getCurrentRetryCount()返回請求的當前重發計數,往往使用這個計數與預定義的最大重試次數比較,一旦達到最大重試次數,就會停止重發。

最為重要的是方法retry(VolleyError error),在請求出現問題時,會被觸發。引數error可以為如下子類,可以用於區別不同的問題原因:

TimeoutError - 超時 AuthFailureError - 鑑權失敗(迴應為401或403) ServerError - 伺服器問題(迴應為5XX) NetworkError - 網路問題(未收到迴應)

(關於Volley如何觸發Retry,可以參考BasicNetwork類的performRequest方法。)

在retry中,應針對錯誤做具體的處理,處理完成後,如果需要重發,待方法直接返回即可,如果要終止重發,需要丟擲error。

給Request指定RetryPolicy

Volley為每個請求設計成可以使用不同的重試策略,這通過方法setRetryPolicy來傳入RetryPolicy 的一個例項。

public Request<?> setRetryPolicy(RetryPolicy retryPolicy)

如果沒有呼叫這個方法,則預設使用DefaultRetryPolicy。

DefaultRetryPolicy

DefaultDetryPolicy可以是RetryPolicy介面的一個最簡單的實現,它被包含了Volley庫中,用作預設的重發機制。

public class DefaultRetryPolicy implements RetryPolicy {
   private int mCurrentTimeoutMs; // timeout值
    private int mCurrentRetryCount; // 重試計數器 
    private final int mMaxNumRetries; // 最大重試次數

   public static final int DEFAULT_TIMEOUT_MS = 2500;
   public static final int DEFAULT_MAX_RETRIES = 1;
   public static final float DEFAULT_BACKOFF_MULT = 1f;

   public DefaultRetryPolicy() {
        this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
    }

   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;
    }

    public float getBackoffMultiplier() {
        return mBackoffMultiplier;
    }

    @Override
    public void retry(VolleyError error) throws VolleyError {
        mCurrentRetryCount++;
        mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
        if (!hasAttemptRemaining()) {
            throw error;
        }
    }

    protected boolean hasAttemptRemaining() {
        return mCurrentRetryCount <= mMaxNumRetries;
    }
}

通過retry方法可以看到,DefaultDetryPolicy的處理策略是,每次被觸發時,將 計數器mCurrentRetryCount加1,當計算器mCurrentRetryCount 未超過最大重試次數mMaxNumRetries時,返回,這時請求將被重發。

注意這時超時時間mCurrentTimeoutMs增加一個比例mBackoffMultiplier,這表示重試時,會允許更久的超時時間,減少Timeout問題發生的機率。

如超過重試次數,則丟擲error,停止重發。

對鑑權失敗執行重發

如果使用OAuth的HTTP API,那麼常常會遇到Access Token失效的情況,這時就要求我們使用Refresh Token重新換取Token,再重發當前請求。 我的做法如下:

   @Override
    public void retry(VolleyError error) throws VolleyError {

        mCurrentRetryCount++;
        mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
        if (!hasAttemptRemaining()) {
            throw error;
        }

        if (error instanceof AuthFailureError  ) {
            Token token = dan.getToken();
            String refreshToken = token.getRefreshToken();

            if( refreshToken !=null && !refreshToken.isEmpty() ) {
                mAuth.refresh(new OnResultListener() {
                    @Override
                    public void onResult(Result result) {
                        if( result.isOK() )
                            mVolleyClient.addToRequestQueue( mRequest );
                    }
                });
            }

            throw error;
        }
    }

在這裡,使用error instanceof AuthFailureError 判斷是否鑑權失敗,如果是,則終止當前請求的重發,通過mAuth.refresh傳送獲取AccessToken請求,並在其正確的迴應中將當前請求在加入佇列。