Volley 소스 다시 보기 (2): 정책 다시 시도

10808 단어
카탈로그
1. 핵심 클래스 2. 재시험 전략 참고 자료
본고는 Volley 프레임워크의 작업 절차에 대해 어느 정도 알고 있는 상황에서 계속 깊이 있게 연구하고자 합니다. 여기에 제가 쓴 윗글인 을 붙여 드리겠습니다.
핵심류
RetryPolicy: Volley 정의된 정책 재시도 요청 인터페이스

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


DefaultRetryPolicy:RetryPolicy의 실현 하위 클래스

public class DefaultRetryPolicy implements RetryPolicy {
    /** The current timeout in milliseconds.       */
    private int mCurrentTimeoutMs; 

    /** The current retry count.       */
    private int mCurrentRetryCount;

    /** The maximum number of attempts.       */
    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        */
    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 {
        //      ++
        mCurrentRetryCount++;
        //        
        mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
        //          ,       VolleyError  
        if (!hasAttemptRemaining()) {
            throw error;
        }
    }

    /**
     *     Request               
     * Returns true if this policy has attempts remaining, false otherwise.
     */
    protected boolean hasAttemptRemaining() {
        return mCurrentRetryCount <= mMaxNumRetries;
    }
}


2. 정책 재시도
원본 코드를 깊이 파고들어 읽으면 Retry 방법이 Volley Error의 이상을 던질 수 있지만 이 방법 내부에서 네트워크 요청을 다시 시작하는 것이 아니라 재시도 정책의 속성을 변경합니다. 예를 들어 시간 초과와 재시도 횟수는 재시도 정책 설정의 제한을 초과하면 달라집니다. 이것은Default Retry Policy에서 검증할 수 있습니다.그렇다면 어떻게 다시 시도할 수 있을까. 우리는 원본 리트리 방법이 호출된 곳을 추적할 수 있고Basic Network의attempt Retry On Exception 방법에 도착했다.

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); //        、       ,              ,       
        } catch (VolleyError e) {
            //         ,     ,        
            request.addMarker(
                    String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
            //            ,     catch  ,      catch     ,           ,         while(true)  
            throw e;
        }
        //       ,        
        request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
    }

}


attempt Retry On Exception 방법이 호출된 곳을 계속 추적하여 Basic Network의perform Request 방법에 도착했습니다.

public class BasicNetwork implements Network {

......

    @Override
    public NetworkResponse performRequest(Request> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            ......

            try {
                ......

                //       、       ,      ,    。 attemptRetryOnException        
                // catch        return,       while(true)  ,              ,           ,               、       
                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) 은perform Request를 호출하여 요청을 실행하고, 이 방법 내부에는while (true) 순환이 있는 것을 기억합니다.
이 방법에서 attempt Retry On Exception은 모두 네 번 호출되었다.
  • SocketTimeoutException이 발생할 때(Socket 통신 시간 초과, 즉 서버에서 데이터를 읽을 때 시간 초과)
  • ConnectTimeoutException이 발생했을 때(요청 시간 초과, 즉 HTTP 서버에 연결하는 시간 초과 또는 HttpConnectionManager가 사용 가능한 연결 시간 초과로 되돌아오기를 기다리는 시간)
  • IOException 발생, 해당 상태 코드 401/403(권한 미통과) 시
  • IOException 발생, 해당 상태 코드 301/302(URL 이전) 발생 시
  • 지금 귀납해 봅시다. 우선 우리가 재시도를 요청하는 정책을 설정(Volley 기본 최대 요청 재시도 횟수는 0, 즉 재시도하지 않음)했다고 가정하고 그 다음에BasicNetwork의perform Request 방법의 바깥쪽은 사실은while 순환입니다. 만약에 네트워크 요청 과정에서 이상이 발생한다면 시간 초과, 인증 미통과 등 상기 네 가지 호출된 이상이catch 이 이상 코드는 다시 시도할 수 있는지 확인합니다. 다시 시도할 수 있다면 이 이상을 삼키고 다음 순환에 들어갑니다. 그렇지 않으면 다시 시도 횟수를 초과하고attempt Retry On Exception 내부에서 이상을 던집니다. 이 때 이전 코드에 의해 처리되고 while 순환을 종료합니다.
    위에서 말한 것은 도대체 어떻게 처리하고while 순환을 종료하는지 의문이 있을 수 있습니다.Basic Network의perform Request 방법은 Volley Error 이상을 포착하지 않았기 때문에try &catch에 살지 않은 이상은 계속 밖으로 던져집니다. 여기서 Network Dispatcher의run 방법에 대해 돌아봅시다.
    public class NetworkDispatcher extends Thread {
    
        ......
    
        @Override
        public void run() {
    
            ......
    
            while (true) {
    
                ......
    
                try {
    
                    ......
    
    
                    // Perform the network request.            ,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에서 다시 시도할 수 없는 Volley Error 이상은NetworkDispatcher에 의해 포착되고 Delivery를 이용하여 사용자가 설정한 Error Listener를 리셋합니다.
    참고 자료
  • android-volley
  • Volley 소스 해석
  • 안드로이드의 volley12_정책 재시도 요청 RetryPolicy 및 DefaultRetryPolicy
  • 좋은 웹페이지 즐겨찾기