Volley (5) - 프레임 소스 분석

25153 단어

전체 실행 프로세스

1. Volley.newRequestQueue(Context); 2. RequestQueue.add(Request); 3.

1. Volley.newRequestQueue(Context) 모듈 내부 실행 프로세스


코드
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }
    public static RequestQueue newRequestQueue(Context context, HttpStack stack)
    {
        return newRequestQueue(context, stack, -1);
    }

내부의 두 개의 구조 함수를 호출하여 다음과 같은 최종 구조 코드에 왔습니다. 여기에 이 있습니다. 잠시 후에 상응하는 해석을 할 것입니다.
    public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException e) {
        }

        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {//   1
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        Network network = new BasicNetwork(stack);//   2
        
        RequestQueue queue;//   3
        if (maxDiskCacheBytes <= -1)
        {
            // No maximum size specified
            queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        }
        else
        {
            // Disk cache size specified
            queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
        }

        queue.start();//   4

        return queue;
    }

상기 코드는 1을 이해한다. 여기의 역할은android 버전을 판단하는 것이다. 2.3보다 크면 HttpURLconnection 기반의 Hurl Stack 대상을 사용하고 그렇지 않으면 Http Client 기반의 Http Client Stack 대상을 사용한다. 2, BasicNetwork 대상은 Stack 네트워크로 데이터를 처리한 다음에 데이터를 간단하게 처리한다. 특히 이상과 비이상을 구분하는 처리는 주로 이상을 던지는지 여부 3이다. 여기는 RequestQueue 대상을 만들고 그 안에 두 개의 Priority BlockingQueue 작용이 while(true)의 사순환적인 실행과 끝에서 튀어나오는 것을 대략 설명한다.이 안에 배포 대상 ExecutorDelivery을 만들었습니다. 이 대상은 네트워크 접근 결과를 주 라인에 나누어 실행하는 역할을 하기 때문에 그 구조 함수에는 주 라인Handler가 포함되어 있습니다.
  • RequestQueue 객체 만들기 기본 코드는 다음과 같습니다.
  •     private final PriorityBlockingQueue> mCacheQueue =
            new PriorityBlockingQueue>();
        private final PriorityBlockingQueue> mNetworkQueue =
            new PriorityBlockingQueue>();
        private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
        public RequestQueue(Cache cache, Network network, int threadPoolSize,
                ResponseDelivery delivery) {
            mCache = cache;
            mNetwork = network;
            mDispatchers = new NetworkDispatcher[threadPoolSize];
            mDelivery = delivery;
        }
        public RequestQueue(Cache cache, Network network, int threadPoolSize) {
            this(cache, network, threadPoolSize,
                    new ExecutorDelivery(new Handler(Looper.getMainLooper())));
        }
    
  • 은 상기 코드에 의하면 mCacheQueuemNetworkQueue은 모두 성명할 때 이미 창설되고 초기화되었다. 요청 결과의 분배 대상인 ExecutorDelivery은 구조할 때 창설된 것이다.
  • 4, RequestQueue를 만드는 대상을 통해 start() 방법을 호출합니다. 방법은 다음과 같습니다.
        public void start() {
            stop();  // Make sure any currently running dispatchers are stopped.
            // Create the cache dispatcher and start it.
            mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
            mCacheDispatcher.start();
    
            // Create network dispatchers (and corresponding threads) up to the pool size.
            for (int i = 0; i < mDispatchers.length; i++) {
                NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                        mCache, mDelivery);
                mDispatchers[i] = networkDispatcher;
                networkDispatcher.start();
            }
        }
    
  • 에서 상기 코드를 통해 우리는 RequestQueue 대상이 캐시 스케줄링 라인을 만드는CacheDispatcher 대상을 볼 수 있다.NetworkDispatcher 객체, 즉 네트워크 스케줄링 스레드 수기본 mDispatchers 그룹의 개수는 DEFAULT_NETWORK_THREAD_POOL_SIZE = 4이기 때문에 5개의 라인이 백엔드에서 실행되고 요청이 오기를 기다리고 있습니다.
  • 그 중에서 NetworkDispatcher와CacheDispatcher는 두 라인의 대상이다. 여기서 우리는 깊이 있는 토론을 하지 않고 잠시 후에 깊이 있게 토론할 것이다. 그러나 이 두 대상은 mCacheQueuemNetworkQueue 두 개의 막힘Queue를 가지고 있음을 알아야 한다.

  • 2. RequestQueue.add(Request) 모듈 내부 실행 프로세스


    코드
        public  Request add(Request request) {
            // Tag the request as belonging to this queue and add it to the set of current requests.
            request.setRequestQueue(this);
            synchronized (mCurrentRequests) {
                mCurrentRequests.add(request);
            }
    
            // Process requests in the order they are added.
            request.setSequence(getSequenceNumber());
            request.addMarker("add-to-queue");
    
            //       ,               
            if (!request.shouldCache()) {
                mNetworkQueue.add(request);
                return request;
            }
    
            //                    ,         
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                if (mWaitingRequests.containsKey(cacheKey)) {
                    // There is already a request in flight. Queue up.
                    Queue> stagedRequests = mWaitingRequests.get(cacheKey);
                    if (stagedRequests == null) {
                        stagedRequests = new LinkedList>();
                    }
                    stagedRequests.add(request);
                    mWaitingRequests.put(cacheKey, stagedRequests);
                    if (VolleyLog.DEBUG) {
                        VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                    }
                } else {
                    //              
                    mWaitingRequests.put(cacheKey, null);
                    mCacheQueue.add(request);
                }
                return request;
            }
        }
    
  • 상술한 코드의 대체적인 절차 작용은 이미 코드에서 설명되었는데 여기에는 군말이 없다.여기에 따르면 캐시를 할 수 있고 이전에 같은 요청이 없었다면 캐시 대기열에 요청을 넣었을 텐데, 이럴 때 어떻게 네트워크 요청을 했는지 의문이 생길 수 있다.여기에 을 표시하십시오. 캐시 대기열에서 해당하는 캐시 데이터를 찾지 못하면 요청을 네트워크 요청 대기열에 다시 추가합니다.
  • 위의 코드를 통해dd방법은 네트워크 요청이나 캐시에서 데이터를 읽는 작업을 하지 않고 요청을 네트워크 요청 대기열이나 캐시 대기열에 넣는 것을 알 수 있다.

  • 위에서 네트워크 요청 request을 네트워크 요청 대기열 mNetworkQueue이나 캐시 대기열 mCacheQueue열에 추가한 후 각각 mNetworkQueuemCacheQueueCacheDispatcher(캐시 스케줄러 라인)과 NetworkDispatcher(네트워크 스케줄러 라인)이 어떻게 두루 다니고 기다리며 어떻게 연결되는지 while(true)의 사순환의 집행과 끝에서 설명했습니다.

    3. 리스트 가입 요청 후 CacheDispatcher와 NetworkDispatcher의 조작


    CacheDispatcher 캐시 스케줄링 스레드의 run() 방법 코드는 다음과 같다.
        public void run() {
            if (DEBUG) VolleyLog.v("start new dispatcher");
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            // Make a blocking call to initialize the cache.
            mCache.initialize();
            Request> request;
            while (true) {
                // release previous request object to avoid leaking request object when mQueue is drained.
                request = null;
                try {
                    //             
                    request = mCacheQueue.take();
                } catch (InterruptedException e) {
                    // We may have been interrupted because it was time to quit.
                    if (mQuit) {
                        return;
                    }
                    continue;
                }
                try {
                    request.addMarker("cache-queue-take");
                    //        ,      .
                    if (request.isCanceled()) {
                        request.finish("cache-discard-canceled");
                        continue;
                    }
                    //      
                    Cache.Entry entry = mCache.get(request.getCacheKey());
                    //      ,            
                    if (entry == null) {
                        request.addMarker("cache-miss");
                        // Cache miss; send off to the network dispatcher.
                        mNetworkQueue.put(request);
                        continue;
                    }
                    //       ,            
                    if (entry.isExpired()) {
                        request.addMarker("cache-hit-expired");
                        request.setCacheEntry(entry);
                        mNetworkQueue.put(request);
                        continue;
                    }
                    //       ,        ,          
                    request.addMarker("cache-hit");
                    Response> response = request.parseNetworkResponse(
                            new NetworkResponse(entry.data, entry.responseHeaders));
                    request.addMarker("cache-hit-parsed");
                    if (!entry.refreshNeeded()) {
                        // Completely unexpired cache hit. Just deliver the response.
                        mDelivery.postResponse(request, response);
                    } else {
                        // Soft-expired cache hit. We can deliver the cached response,
                        // but we need to also send the request to the network for
                        // refreshing.
                        request.addMarker("cache-hit-refresh-needed");
                        request.setCacheEntry(entry);
                        // Mark the response as intermediate.
                        response.intermediate = true;
                        // Post the intermediate response back to the user and have
                        // the delivery then forward the request along to the network.
                        final Request> finalRequest = request;
                        mDelivery.postResponse(request, response, new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    mNetworkQueue.put(finalRequest);
                                } catch (InterruptedException e) {
                                    // Not much we can do about this.
                                }
                            }
                        });
                    }
                } catch (Exception e) {
                    VolleyLog.e(e, "Unhandled exception %s", e.toString());
                }
            }
        }
    
  • 위의 코드 실행 절차는 대체적으로 주석으로 표시되었다.시작은 캐시 대기열에서 요청을 꺼내서 취소했는지 판단하고 취소 요청이 없으면 캐시를 찾으러 가는 것입니다.캐시가 없으면 네트워크 요청 대기열에 요청을 추가합니다.캐시가 있으면 캐시가 만료되었는지 판단하고, 만료되었을 경우 네트워크 요청 대기열에 가입합니다.기한이 지나지 않으면 request.parseNetworkResponse()을 사용하여 데이터를 분석하고 mDelivery.postResponse(request, response)을 통해 결과를 주 라인으로 되돌려줍니다.
  • 위의 request.parseNetworkResponse()은 모든 요청 유형이 인터페이스 Request에 대한 실현이고 그 안에서 서로 다른 장면에 따라 데이터를 서로 다른 유형으로 해석한다. 예를 들어 String 유형, Bitmap 유형
  • Delivery.postResponse()의 역할은 데이터의 성공 여부를 간단하게 분석한 다음에 request.deliverResponse 또는 request.deliverError을 통해 데이터를 SuccessListener 또는 ErrorListener에 나누어 주고 대응하는 onResponse()onErrorResponse()을 호출하여 우리가 원하는 곳에 결과를 최종적으로 전달하는 것이다.

  • NetworkDispatcher 캐시 스케줄링 스레드의 run() 메소드 코드는 다음과 같습니다.
        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            Request> request;
            while (true) {
                long startTimeMs = SystemClock.elapsedRealtime();
                // release previous request object to avoid leaking request object when mQueue is drained.
                request = null;
                try {
                    //         .
                    request = mQueue.take();
                } catch (InterruptedException e) {
                    // We may have been interrupted because it was time to quit.
                    if (mQuit) {
                        return;
                    }
                    continue;
                }
    
                try {
                    request.addMarker("network-queue-take");
    
                    //           ,        
                    if (request.isCanceled()) {
                        request.finish("network-discard-cancelled");
                        continue;
                    }
    
                    addTrafficStatsTag(request);
    
                    //       
                    NetworkResponse networkResponse = mNetwork.performRequest(request);
                    request.addMarker("network-http-complete");
    
                    // If the server returned 304 AND we delivered a response already,
                    // we're done -- don't deliver a second identical response.
                    if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                        request.finish("not-modified");
                        continue;
                    }
    
                    //        .
                    Response> response = request.parseNetworkResponse(networkResponse);
                    request.addMarker("network-parse-complete");
    
                    // Write to cache if applicable.
                    // TODO: Only update cache metadata instead of entire record for 304s.
                    if (request.shouldCache() && response.cacheEntry != null) {
                        mCache.put(request.getCacheKey(), response.cacheEntry);
                        request.addMarker("network-cache-written");
                    }
    
                    // Post the response back.
                    request.markDelivered();
                    //         
                    mDelivery.postResponse(request, response);
                } catch (VolleyError volleyError) {
                    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);
                }
            }
        }
    
  • 위의 코드는 주로 요청이 취소되었는지 판단한 다음에 네트워크를 계속 요청할지 결정하고 데이터를 주 라인에 되돌려줍니다.
  • 은 네트워크 요청의 실행 문구를 mNetwork.performRequest(request)으로 강조하는데 그의 실현 유형은 우리가 이전에 전달한 BasicNetWork 유형이다.
  • 요청 성공 결과는 앞에서 말한 요청 캐시 처리와 동일합니다. Delivery.postResponse()을 통해 결과를 메인 라인
  • 으로 리셋합니다.
    BasicNetwork를 살펴보겠습니다.performRequest() 메서드
        @Override
        public NetworkResponse performRequest(Request> request) throws VolleyError {
            long requestStart = SystemClock.elapsedRealtime();
            while (true) {
                HttpResponse httpResponse = null;
                byte[] responseContents = null;
                Map responseHeaders = Collections.emptyMap();
                try {
                    // Gather headers.
                    Map headers = new HashMap();
                    addCacheHeaders(headers, request.getCacheEntry());
                    httpResponse = mHttpStack.performRequest(request, headers);
                    StatusLine statusLine = httpResponse.getStatusLine();
                    int statusCode = statusLine.getStatusCode();
    
                    responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                    // Handle cache validation.
                    if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
    
                        Entry entry = request.getCacheEntry();
                        if (entry == null) {
                            return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                                    responseHeaders, true,
                                    SystemClock.elapsedRealtime() - requestStart);
                        }
    
                        // A HTTP 304 response does not have all header fields. We
                        // have to use the header fields from the cache entry plus
                        // the new ones from the response.
                        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                        entry.responseHeaders.putAll(responseHeaders);
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                                entry.responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);
                    }
                    
                    // Handle moved resources
                    if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                        String newUrl = responseHeaders.get("Location");
                        request.setRedirectUrl(newUrl);
                    }
    
                    // Some responses such as 204s do not have content.  We must check.
                    if (httpResponse.getEntity() != null) {
                      responseContents = entityToBytes(httpResponse.getEntity());
                    } else {
                      // Add 0 byte response as a way of honestly representing a
                      // no-content request.
                      responseContents = new byte[0];
                    }
    
                    // if the request is slow, log it.
                    long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                    logSlowRequests(requestLifetime, request, responseContents, statusLine);
    
                    if (statusCode < 200 || statusCode > 299) {
                        throw new IOException();
                    }
                    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 (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) {
                            attemptRetryOnException("auth",
                                    request, new AuthFailureError(networkResponse));
                        } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || 
                                    statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                            attemptRetryOnException("redirect",
                                    request, new RedirectError(networkResponse));
                        } else {
                            // TODO: Only throw ServerError for 5xx status codes.
                            throw new ServerError(networkResponse);
                        }
                    } else {
                        throw new NetworkError(e);
                    }
                }
            }
        }
    
  • mHttpStack.performRequest(request, headers) 방법으로 진정한 네트워크 요청을 할 것이다. 그 중에서 mHttpStack의 대상은 처음에 Volley.newRequestQueue()이 호출되었을 때 서로 다른 버전에 따라 생성된 HurlStack 또는 HttpClientStack이다.
  • 그리고 여기서 많은 네트워크 요청 이상을 NetworkDispatcher 처리에 맡기고 NetworkDispatcher는 ExecutorDelivery에게 메인 라인
  • 으로 리셋합니다.
    결과를 주 라인에 되돌려 주는 방법: Delivery.postResponse() 이것은 위에서 말한 결과를 주 라인에 되돌려 주는 ExecutorDelivery입니다. 코드만 붙이고 원래 설명을 아래에 복사해서 붙여 넣습니다.
        @Override
        public void postResponse(Request> request, Response> response) {
            postResponse(request, response, null);
        }
    
        @Override
        public void postResponse(Request> request, Response> response, Runnable runnable) {
            request.markDelivered();
            request.addMarker("post-response");
            mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
        }
    
        private class ResponseDeliveryRunnable implements Runnable {
            private final Request mRequest;
            private final Response mResponse;
            private final Runnable mRunnable;
    
            public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
                mRequest = request;
                mResponse = response;
                mRunnable = runnable;
            }
    
            @SuppressWarnings("unchecked")
            @Override
            public void run() {
                // If this request has canceled, finish it and don't deliver.
                if (mRequest.isCanceled()) {
                    mRequest.finish("canceled-at-delivery");
                    return;
                }
    
                // Deliver a normal response or error, depending.
                if (mResponse.isSuccess()) {
                    mRequest.deliverResponse(mResponse.result);
                } else {
                    mRequest.deliverError(mResponse.error);
                }
    
                // If this is an intermediate response, add a marker, otherwise we're done
                // and the request can be finished.
                if (mResponse.intermediate) {
                    mRequest.addMarker("intermediate-response");
                } else {
                    mRequest.finish("done");
                }
    
                // If we have been provided a post-delivery runnable, run it.
                if (mRunnable != null) {
                    mRunnable.run();
                }
           }
        }
    
  • Delivery.postResponse()의 역할은 데이터의 성공 여부를 간단하게 분석한 다음에 request.deliverResponse 또는 request.deliverError을 통해 데이터를 SuccessListener 또는 ErrorListener에 나누어 주고 그에 대응하는 onResponse()onErrorResponse()을 호출하여 최종적으로 우리가 원하는 곳에
  • 을 전달하는 것이다.
    StringRequest의 일부 코드
    public class StringRequest extends Request {
        private Listener mListener;
        public StringRequest(int method, String url, Listener listener,
                ErrorListener errorListener) {
            super(method, url, errorListener);
            mListener = listener;
        }
        public StringRequest(String url, Listener listener, ErrorListener errorListener) {
            this(Method.GET, url, listener, errorListener);
        }
        @Override
        protected void deliverResponse(String response) {
            if (mListener != null) {
                mListener.onResponse(response);
            }
        }
    
  • 은 주로 deliverResponse() 방법을 보고 결과를 mListener에 주고 onResponse() 방법을 호출하여 StringRequest 대상을 실례화한 곳으로 되돌려준다.StringRequest의 인스턴스화
  •         StringRequest mStringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener() {
                @Override
                public void onResponse(String response) {
                    textView.setText(response);
                }
            }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError volleyError) {
                    textView.setText(volleyError.getMessage());
                }
            });
            queue.add(mStringRequest);
    
  • 여기서 Response.ListenerResponse.ErrorListener() 인터페이스를 구현하였으며 최종 결과는 여기로 되돌려집니다.

  • 이로써 Volley 프레임워크의 원본 코드 해석이 끝났습니다.


    Volley-네트워크 통신 프레임워크 디렉토리

    좋은 웹페이지 즐겨찾기