Volley原始碼分析【面向介面程式設計的典範】

王世暉發表於2016-06-03

基本原理

Volley採用生產者消費者模型,生產者(Volley的使用者)通過呼叫add方法給請求佇列新增請求,快取排程器和網路排程器作為消費者從請求佇列取出請求處理,根據不同情況決定走快取還是走網路請求資料,最後切換執行緒,將請求的資料回撥給UI執行緒。

建立請求佇列

Volley通過靜態工廠方法newRequestQueue生成一個請求佇列RequestQueue

    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        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) {
                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);

        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

根據應用的包名和版本號建立了userAgent
根據android的版本號建立http棧HttpStack
依據http棧建立一個BasicNetwork物件,接著建立一個DiskBasedCache物件

有了快取和網路,就可以建立請求佇列RequestQueue了

   RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);

然後呼叫請求佇列的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();
        }
    }

可見請求佇列的start方法啟動了一個快取排程器執行緒和若干個(預設4個)網路排程器執行緒

快取排程器CacheDispatcher分析

快取排程器繼承自Thread,因此實際上是一個執行緒

public class CacheDispatcher extends Thread

CacheDispatcher充分體現了面向介面程式設計的精髓,CacheDispatcher所依賴的屬性全部是介面,而不是具體的實現,通過建構函式進行以來注入

    /** The queue of requests coming in for triage. */
    private final BlockingQueue<Request<?>> mCacheQueue;

    /** The queue of requests going out to the network. */
    private final BlockingQueue<Request<?>> mNetworkQueue;

    /** The cache to read from. */
    private final Cache mCache;

    /** For posting responses. */
    private final ResponseDelivery mDelivery;

BlockingQueue、Cache、ResponseDelivery均為介面而不是具體的實現,這樣降低了類之間的耦合,提高了程式設計的靈活性。

   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();

        while (true) {
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");

                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // Attempt to retrieve this item from cache.
                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 it is completely expired, just send it to the network.
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // We have a cache hit; parse its data for delivery back to the request.
                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.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }

快取排程器執行緒在設定了執行緒優先順序和初始化了快取後就進入死迴圈,從mCacheQueue不斷地獲取請求並進行處理。
如果取出的請求已經被取消了,那麼結束此請求,跳過本次迴圈,接著從佇列中取出下一個請求處理

      if (request.isCanceled()) {
          request.finish("cache-discard-canceled");
          continue;
      }

如果取出的請求沒有被取消,就在快取中查詢是否已經快取該請求,快取不命中,就把此請求丟到網路請求佇列,從網路獲取資料,然後跳出本次迴圈,接著從快取請求佇列讀取下一個請求處理

      // Attempt to retrieve this item from cache.
      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 it is completely expired, just send it to the network.
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

如果快取沒有過期,並且不需要重新整理,那麼直接將快取中的資料post給UI執行緒,避免了一次網路請求

        if (!entry.refreshNeeded()) {
           // Completely unexpired cache hit. Just deliver the response.
           mDelivery.postResponse(request, response);
        } 

如果快取沒有過期,但是需要重新整理,這時候據先把快取post給UI執行緒,然後後臺去默默地請求網路資料,網路排程器會在請求到最新資料後重新整理快取和UI資料。

            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.
               mDelivery.postResponse(request, response, new Runnable() {
                   @Override
                   public void run() {
                       try {
                           mNetworkQueue.put(request);
                       } catch (InterruptedException e) {
                           // Not much we can do about this.
                       }
                   }
               });
           }
    /**
     * Parses a response from the network or cache and delivers it. The provided
     * Runnable will be executed after delivery.
     */
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable);

postResponse方法會先將快取中的資料deliver給UI執行緒,然後執行Runnable,也就是 mNetworkQueue.put(request),通過網路獲取最新資料

網路排程器NetworkDispatcher分析

NetworkDispatcher網路排程器CacheDispatcher快取排程器非常類似,都是繼承自Thread,都是面向介面程式設計的典範。

    /** The queue of requests to service. */
    private final BlockingQueue<Request<?>> mQueue;
    /** The network interface for processing requests. */
    private final Network mNetwork;
    /** The cache to write to. */
    private final Cache mCache;
    /** For posting responses and errors. */
    private final ResponseDelivery mDelivery;

網路排程器從網路請求佇列mQueue中不斷獲取請求,通過mNetwork獲取網路最新資料,把最新資料寫入快取mCache,通過mDelivery切換執行緒,把資料回撥給UI執行緒

   public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // Take a request from the queue.
                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 the request was cancelled already, do not perform the
                // network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                // Perform the network 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;
                }

                // Parse the response here on the worker thread.
                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);
            }
        }
    }

網路排程器執行緒在設定了執行緒優先順序後就進入死迴圈,不斷從網路請求佇列讀取請求進行處理
如果取出的請求已被取消,那麼結束此請求,結束本次迴圈,從佇列讀取下一個請求進行處理

     // If the request was cancelled already, do not perform the
     // network request.
     if (request.isCanceled()) {
         request.finish("network-discard-cancelled");
         continue;
     }

該請求沒有被取消的話,通過mNetwork進行網路資料訪問

    // Perform the network request.
    NetworkResponse networkResponse = mNetwork.performRequest(request);

伺服器返回304,表示請求的資源和上次相比沒有修改。
如果伺服器返回304,並且該請求已經之前已經請求到了response,那麼之前的response可以複用,沒必要重新網路請求,結束本次迴圈,從網路請求佇列獲取下一條請求進行處理

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

/**
* Returns true if this request has had a response delivered for it.
*/
public boolean hasHadResponseDelivered()

如果伺服器返回的狀態碼不是304,說明請求的資料在伺服器上已經發生了變化,需要重新並解析獲取response

    // Parse the response here on the worker thread.
    Response<?> response = request.parseNetworkResponse(networkResponse);

如果該request需要快取並且response沒有出錯,就把這次網路請求的response寫入快取

    if (request.shouldCache() && response.cacheEntry != null) {
        mCache.put(request.getCacheKey(), response.cacheEntry);
        request.addMarker("network-cache-written");
    }

最後把response傳遞給使用者即可

    mDelivery.postResponse(request, response);

如果網路請求失敗,同樣也罷請求失敗的情況傳遞給使用者,讓使用者自己處理

    mDelivery.postError(request, volleyError);

RequestQueue的屬性分析

  /** Used for generating monotonically-increasing sequence numbers for requests. */
    private AtomicInteger mSequenceGenerator = new AtomicInteger();

新增進請求佇列的每一個請求都會建立全域性唯一的一個序列號,使用AtomicInteger型別的原子整數保證多執行緒併發新增請求的情況下不會出現序列號的重複

    /**
     * Staging area for requests that already have a duplicate request in flight.
     *containsKey(cacheKey) indicates that there is a request in flight for the given cache key.
     get(cacheKey) returns waiting requests for the given cache key. The in flight request is not contained in that list. Is null if no requests are staged.
     */
    private final Map<String, Queue<Request<?>>> mWaitingRequests =
            new HashMap<String, Queue<Request<?>>>();

mWaitingRequests是一個Map,鍵是cache key,值是一個佇列,該佇列包含了所有等待針對cache key請求結果的的請求,即同一個url的重複請求佇列

    /**
     * The set of all requests currently being processed by this RequestQueue. A Request
     * will be in this set if it is waiting in any queue or currently being processed by
     * any dispatcher.
     */
    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

mCurrentRequests是一個集合,裡邊包含了所有正在進行中的請求。

    /** The cache triage queue. */
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =
        new PriorityBlockingQueue<Request<?>>();

    /** The queue of requests that are actually going out to the network. */
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<Request<?>>();

兩個優先順序佇列,快取請求佇列和網路請求佇列

生產者新增請求

 public <T> Request<T> add(Request<T> 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 the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return 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());

如果該請求被設定不快取,那麼直接跳過快取排程器,把該請求新增到網路請求佇列即可。

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

然後判斷之前是否已經有針對此cache key的重複請求(相同url的請求),有的話直接放入該cache key的等待佇列(重複請求佇列),避免多次請求同一個url。

   if (mWaitingRequests.containsKey(cacheKey)) {
            // There is already a request in flight. Queue up.
            Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
            if (stagedRequests == null) {
                stagedRequests = new LinkedList<Request<?>>();
            }
            stagedRequests.add(request);
            mWaitingRequests.put(cacheKey, stagedRequests);
            }

如果不是重複的請求,這個請求是第一次新增到佇列的,那麼,設定該該請求的cache key的等待佇列(重複請求佇列)是null,將該請求新增進快取佇列

    // Insert 'null' queue for this cacheKey, indicating there is now a request in
    // flight.
    mWaitingRequests.put(cacheKey, null);
    mCacheQueue.add(request);

執行緒切換,把請求結果遞交給使用者ResponseDelivery

ResponseDelivery是一個介面,有一個直接實現類ExecutorDelivery

public class ExecutorDelivery implements ResponseDelivery

    /** Used for posting responses, typically to the main thread. */
    private final Executor mResponsePoster;

    /**
     * Creates a new response delivery interface.
     * @param handler {@link Handler} to post responses on
     */
    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

ExecutorDelivery建構函式需要傳入一個handler,通過handler達到執行緒切換的目的,如果傳入的handler繫結的是UI執行緒的Looper,那麼command任務將在UI執行緒被執行

    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

postResponse方法建立了一個ResponseDeliveryRunnable型別的任務傳遞給execute方法,導致ResponseDeliveryRunnable該任務將在UI執行緒被執行,ResponseDeliveryRunnable主要在UI執行緒做了什麼工作呢?

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

這裡按照請求執行的結果分類進行了處理,如果該請求已經被取消了那麼直接呼叫該請求的finish方法然後直接返回

        if (mRequest.isCanceled()) {
            mRequest.finish("canceled-at-delivery");
            return;
        }

請求成功的話,呼叫mRequest的deliverResponse方法,請求失敗的話呼叫mRequest的deliverError方法

       // Deliver a normal response or error, depending.
       if (mResponse.isSuccess()) {
           mRequest.deliverResponse(mResponse.result);
       } else {
           mRequest.deliverError(mResponse.error);
       }

mRequest的型別Request,而Request是一個抽象的型別,實際執行中mRequest的型別將會是StringRequest、JsonRequest等等預設實現類或者使用者自己繼承Request實現的具體邏輯,充分體現了面向抽象程式設計,而不是面向具體程式設計。
現在知道了在主執行緒中會呼叫mRequest的deliverResponse方法(假定請求成功),以StringRequest為例,看看deliverResponse到底做了什麼

    @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }
    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }
}

可見在deliverResponse方法中呼叫了回撥介面的onResponse方法,mListener通過StringRequest的建構函式通過使用者傳進來,這樣請求成功的話,使用者可以直接在實現的Listener的onResponse方法中獲取到請求成功的String結果。
從哪裡可以看出請求結果是通過handler傳遞給UI執行緒處理的呢,通過RequestQueue的構造器

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

new ExecutorDelivery(new Handler(Looper.getMainLooper()))

通過Looper.getMainLooper()給handler繫結了UI執行緒的Looper,這樣請求結果就被切換到UI執行緒中傳遞給使用者.

相關文章