Volley 源碼解析: 一款 Google 推出的網絡請求框架

本篇源碼解析基於 Volley 1.1.1

個人博客:https://blog.N0tExpectErr0r.cn

小專欄:https://xiaozhuanlan.com/N0tExpectErr0r

Volley 是 Google 開發的一款網絡請求框架,目前已停止更新。雖然目前大家的關注焦點都在 Retrofit、OkHttp 等第三方網絡請求框架,團隊的項目中所用的也是這兩個框架,但 Volley 中還是有非常多優秀的設計思想值得我們去學習的。因此今天準備來學習一下 Volley 的源碼,瞭解一下它的核心設計思想。

Volley

我們先看到 Volley 的入口——Volley 類。

創建 RequestQueue

Volley 在使用之前,我們需要一個請求隊列對象 RequestQueue,一般整個應用統一用同一個 RequestQueue,讓我們看看創建它的方法 Volley.newRequestQueue(Context)

/**
 * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
 *
 * @param context A {@link Context} to use for creating the cache dir.
 * @return A started {@link RequestQueue} instance.
 */
public static RequestQueue newRequestQueue(Context context) {
    return newRequestQueue(context, (BaseHttpStack) null);
}

它轉調到了 Volley.newRequestQueue(Context, BaseHttpStack) 方法,同時還有一個 Volley.newRequestQueue(Context, HttpStack) 方法:

/**
 * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
 *
 * @param context A {@link Context} to use for creating the cache dir.
 * @param stack An {@link HttpStack} to use for the network, or null for default.
 * @return A started {@link RequestQueue} instance.
 * @deprecated Use {@link #newRequestQueue(Context, BaseHttpStack)} instead to avoid depending
 *     on Apache HTTP. This method may be removed in a future release of Volley.
 */
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
    if (stack == null) {
        return newRequestQueue(context, (BaseHttpStack) null);
    }
    return newRequestQueue(context, new BasicNetwork(stack));
}

從上面的註釋可以看出,HttpStack 是一個用於在 NetWork 中使用的對象,如果傳入的 stack 不爲 null,則會調用到 Volley.newRequestQueue(Context, Network),否則它同樣會轉調到 Volley.newRequestQueue(Context, BaseHttpStack)

/**
 * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
 *
 * @param context A {@link Context} to use for creating the cache dir.
 * @param stack A {@link BaseHttpStack} to use for the network, or null for default.
 * @return A started {@link RequestQueue} instance.
 */
public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
    BasicNetwork network;
    // 創建 NetWork 對象
    if (stack == null) {
   			// stack 爲空則創建一個Stack,然後再創建 NetWork
    		// 高版本下,HttpStack 使用的是 HurlStack,低版本下使用 HttpClientStack
        if (Build.VERSION.SDK_INT >= 9) {
            network = new BasicNetwork(new HurlStack());
        } else {
            // Prior to Gingerbread, HttpUrlConnection was unreliable.
            // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
            // At some point in the future we'll move our minSdkVersion past Froyo and can
            // delete this fallback (along with all Apache HTTP code).
            String userAgent = "volley/0";
            try {
                String packageName = context.getPackageName();
                PackageInfo info =
                        context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
                userAgent = packageName + "/" + info.versionCode;
            } catch (NameNotFoundException e) {
            }
            network =
                    new BasicNetwork(
                            new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
        }
    } else {
        network = new BasicNetwork(stack);
    }
    return newRequestQueue(context, network);
}

這個方法主要做的事情就是根據 stack 創建 netWork 對象。

stack 爲空時,對於高於 9 的 SDK 版本,使用 HurlStack,而對於低於它的版本則使用 HttpClientStack。根據上面的註釋可以看出這樣做的原因:因爲在 SDK 9 之前的 HttpUrlConnection 不是很可靠。(我們可以推測在高版本 (SDK > 9)Volley 基於 HttpUrlConnection 實現,低版本則基於 HttpClient 實現。

另外這裏可能會比較疑惑,NetWorkHttpStack 都是用來做什麼的?這些問題我們後面都會一一解決。

它最後同樣調用到了 Volley.newRequestQueue(Context, Network)

private static RequestQueue newRequestQueue(Context context, Network network) {
    final Context appContext = context.getApplicationContext();
    // Use a lazy supplier for the cache directory so that newRequestQueue() can be called on
    // main thread without causing strict mode violation.
    DiskBasedCache.FileSupplier cacheSupplier =
            new DiskBasedCache.FileSupplier() {
                private File cacheDir = null;
                @Override
                public File get() {
                    if (cacheDir == null) {
                        cacheDir = new File(appContext.getCacheDir(), DEFAULT_CACHE_DIR);
                    }
                    return cacheDir;
                }
            };
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheSupplier), network);
    queue.start();
    return queue;
}

首先根據 Context 獲取到了應用緩存文件夾並創建 Cache 文件。這裏有個比較小的細節,爲了在 Application 調用 newRequestQueue 同時又不被 StrictMode 有關文件操作相關的規則所影響,Volley 中使用了一個 FileSupplier 來對 File 進行包裝,它採用了一個懶創建的思路,只有用到的時候才創建對應的 cacheDir

之後構造了 RequestQueue,調用了其 start 方法並對其返回。可以看出來,Volley 是一個 RequestQueue 的靜態工廠。

RequestQueue

RequestQueue 中維護了三個容器:兩個 PriorityBlockingQueuemCacheQueuemNetQueue,以及一個 SetmCurrentRequests

  • mCacheQueue:用於存放緩存的待請求的 Request
  • mNetQueue :用於存放等待發起的 Request
  • mCurrentQueue:用於存放當前正在進行請求的 Request

創建

我們先來看看 RequestQueue 的構造函數:

public RequestQueue(Cache cache, Network network) {
    this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}

它轉調到了另一個構造函數,並傳遞了一個默認線程數 DEFAULT_NETWORK_THREAD_POOL_SIZE,它代表了網絡請求分派線程啓動時的默認個數,默認值爲 4。

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(
            cache,
            network,
            threadPoolSize,
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

這個構造函數創建了一個主線程的 Handler 並用它構建了一個 ExecutorDelivery,關於 ExecutorDelivery 我們後面再討論,它是一個用於交付 Response 和 Error 信息的類。

後面轉調的構造函數主要是進行一些賦值。

啓動

接着我們看看 RequestQueue.start,看看它是如何啓動的:

/** Starts the dispatchers in this queue. */
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 的主要用途是啓動該 Queue 中的 Dispatcher。它的主要步驟如下:

  1. 調用 stop 方法停止所有正在運行的 Dispatcher
  2. 創建 CacheDispatcher 並啓動。
  3. 創建前面指定個數的 NetworkDispatcher(默認爲 4 個)並啓動。

可以看出來,每個 RequestQueue 中共有 5 個 Dispatcher,其中有 4 個 NetworkDispatcher 和 1 個 CacheDispatcher

入隊

我們可以通過 RequestQueue.add 將一個 Request 入隊,它會根據當前 Request 是否需要進行緩存將其加入 mNetworkQueuemCacheQueue(這裏實際上 GET 請求首先會放入 mCacheQueue,其餘請求直接放入 mNetworkQueue

/**
 * Adds a Request to the dispatch queue.
 *
 * @param request The request to service
 * @return The passed-in 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");
    sendRequestEvent(request, RequestEvent.REQUEST_QUEUED);
    // If the request is uncacheable, skip the cache queue and go straight to the network.
    if (!request.shouldCache()) {
        mNetworkQueue.add(request);
        return request;
    }
    mCacheQueue.add(request);
    return request;
}

停止

我們先看看 stop 做了什麼:

public void stop() {
    if (mCacheDispatcher != null) {
        mCacheDispatcher.quit();
    }
    for (final NetworkDispatcher mDispatcher : mDispatchers) {
        if (mDispatcher != null) {
            mDispatcher.quit();
        }
    }
}

這裏僅僅是對每個 Dispatcher 調用了其 quit 方法。

結束

RequestQueue 還有個 finish 方法,對應了 Request.finish

/**
 * Called from {@link Request#finish(String)}, indicating that processing of the given request
 * has finished.
 */
@SuppressWarnings("unchecked") // see above note on RequestFinishedListener
<T> void finish(Request<T> request) {
    // Remove from the set of requests currently being processed.
    synchronized (mCurrentRequests) {
        mCurrentRequests.remove(request);
    }
    synchronized (mFinishedListeners) {
        for (RequestFinishedListener<T> listener : mFinishedListeners) {
            listener.onRequestFinished(request);
        }
    }
    sendRequestEvent(request, RequestEvent.REQUEST_FINISHED);
}

主要是將結束的 RequestmCurrentRequests 中移除,並調用外部註冊的回調以及發送 REQUEST_FINISHED 事件。

ExecutorDelivery

接下來我們看看 ExecutorDelivery 究竟是做什麼的

/** Delivers responses and errors. */
public class ExecutorDelivery implements ResponseDelivery {
		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 的主要作用是交付 Response 和 Error 信息。

它內部持有了一個名爲 ResponsePosterExecutor,每個調用這個 Posterexecute 方法的 Runnable 都會通過 Handler.post 發送到主線程的 MessageQueue 中。

接着我們看到它內部的方法:

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

@Override
public void postError(Request<?> request, VolleyError error) {
    request.addMarker("post-error");
    Response<?> response = Response.error(error);
    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
}

可以看出來,它內部的方法主要是將 Response 信息和 Error 信息 post 到 MessageQueue 中,其中 requestresponse 會被包裝爲一個 ResponseDeliveryRunnable

ResponseDeliveryRunnable

ResponseDeliveryRunnableExecutorDelivery 的一個內部類,可以看到它的 run 方法:

@Override
public void run() {
    // NOTE: If cancel() is called off the thread that we're currently running in (by
    // default, the main thread), we cannot guarantee that deliverResponse()/deliverError()
    // won't be called, since it may be canceled after we check isCanceled() but before we
    // deliver the response. Apps concerned about this guarantee must either call cancel()
    // from the same thread or implement their own guarantee about not invoking their
    // listener after cancel() has been called.
    // 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();
    }
}

這裏會根據 RequestResponse 的不同狀態調用 Request 中的不同方法以對 Response 和 Error 的結果進行交付:

  • 如果 Request 被取消,調用 Request.finish 方法直接結束。
  • 如果請求成功,調用 Request.deliverResponse 方法反饋 Response
  • 如果請求失敗,調用 Request.deliverError 方法反饋 Error
  • 如果該請求只是一箇中間請求,則不結束該請求,而是給它加上一個 “intermediate-response” 標記,否則結束該請求。
  • 如果 postResponse 方法傳遞了一個 Runnbale 進來,則執行該 Runnable

NetworkDispatcher

我們接着來看到 NetworkDispatcher,它繼承自 Thread,我們先看到其 run 方法:

@Override
public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    while (true) {
        try {
            processRequest();
        } catch (InterruptedException e) {
            // We may have been interrupted because it was time to quit.
            if (mQuit) {
                Thread.currentThread().interrupt();
                return;
            }
            VolleyLog.e(
                    "Ignoring spurious interrupt of NetworkDispatcher thread; "
                            + "use quit() to terminate it");
        }
    }
}

它在不斷循環調用 processRequest 方法:

// Extracted to its own method to ensure locals have a constrained liveness scope by the GC.
// This is needed to avoid keeping previous request references alive for an indeterminate amount
// of time. Update consumer-proguard-rules.pro when modifying this. See also
// https://github.com/google/volley/issues/114
private void processRequest() throws InterruptedException {
    // Take a request from the queue.
    Request<?> request = mQueue.take();
    processRequest(request);
}

這裏首先從 mQuqueRequestQueue 中的 NetQuque)中取出了 Request,雖然 NetworkDispacher 是多個同時執行,但由於使用了 BlockingQueue 因此不用考慮線程安全問題。

可以發現這是一種典型的生產者消費者模型,多個 NetworkDispatcher 不斷地從 NetQueue 中取出 Request 並進行網絡請求,用戶就是網絡請求的生產者,而 NetworkDispatcher 就是網絡請求的消費者。

processRequest 方法繼續調用了 processRequest(Request) 方法:

void processRequest(Request<?> request) {
    long startTimeMs = SystemClock.elapsedRealtime();
    // 發送 REQUEST_NETWORK_DISPATCH_STARTED 事件
    request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
    try {
        request.addMarker("network-queue-take");
        // 如果這個 request 已經被取消,則 finish 它並通知 Listener Response 沒有用
        if (request.isCanceled()) {
            request.finish("network-discard-cancelled");
            request.notifyListenerResponseNotUsable();
            return;
        }
        addTrafficStatsTag(request);
        // 調用 mNetwork.performRequest 發起同步請求拿到 Response
        NetworkResponse networkResponse = mNetwork.performRequest(request);
        request.addMarker("network-http-complete");
        // 如果返回了 304 並且我們已經交付了 Reponse,則 finish 它並通知 Listener Response 沒有用
        if (networkResponse.notModified && request.hasHadResponseDelivered()) {
            request.finish("not-modified");
            request.notifyListenerResponseNotUsable();
            return;
        }
        // 對 response 進行轉換
        Response<?> response = request.parseNetworkResponse(networkResponse);
        request.addMarker("network-parse-complete");
        // 如果適用,寫入緩存
        if (request.shouldCache() && response.cacheEntry != null) {
            mCache.put(request.getCacheKey(), response.cacheEntry);
            request.addMarker("network-cache-written");
        }
        // 通過 ExecutorDelivery 對 Response 進行交付並將 request 標記爲已交付
        request.markDelivered();
        mDelivery.postResponse(request, response);
        // 通知 Listener 已獲得 Response
        request.notifyListenerResponseReceived(response);
    } catch (VolleyError volleyError) {
        volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
        // 交付 Error 信息並通知 Listener Response 沒有用
        parseAndDeliverNetworkError(request, volleyError);
        request.notifyListenerResponseNotUsable();
    } catch (Exception e) {
        VolleyLog.e(e, "Unhandled exception %s", e.toString());
        VolleyError volleyError = new VolleyError(e);
        volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
        // 交付 Error 信息並通知 Listener Response 沒有用
        mDelivery.postError(request, volleyError);
        request.notifyListenerResponseNotUsable();
    } finally {
    		// 發送 REQUEST_NETWORK_DISPATCH_FINISHED 事件
        request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED);
    }
}

上面的代碼比較長,我們可以將其分爲如下的步驟:

  1. 發送 REQUEST_NETWORK_DISPATCH_STARTED 事件,表示請求已開始
  2. 如果 request 已經被取消,則 finish 它並通知 Listener Response 沒有用,不再繼續進行
  3. 調用 mNetwork.performRequest 發起同步請求拿到 Response
  4. 如果返回了 304 並且我們已經交付了 Reponse,則 finish 它並通知 Listener Response 沒有用,不再繼續進行
  5. Response 進行轉換
  6. 如果該 Request 可以進行緩存,以 Request 爲 key,Response 爲 value 進行緩存
  7. 通過 ExecutorDeliveryResponse 進行交付並將 Request 標記爲已交付
  8. 通知 Listener 已獲得 Response
  9. 如果出現了異常,交付 Error 信息並通知 Listener Response 沒有用,其中所有 Error 都需要轉換成 VollyError 類。
  10. 發送 REQUEST_NETWORK_DISPATCH_FINISHED 事件,表示請求已結束

從上面的步驟我們可以得到一些信息:

  1. 有一套 Event 機制用於傳送各種事件
  2. Network 類是網絡請求真正的實現類。
  3. Response 有個轉換的問題
  4. 存在一套 Response 緩存機制。

CacheDispatcher

CacheDispatcher 同樣繼承自 Thread,我們先看到其 run 方法:

@Override
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 {
            processRequest();
        } catch (InterruptedException e) {
            // We may have been interrupted because it was time to quit.
            if (mQuit) {
                Thread.currentThread().interrupt();
                return;
            }
            VolleyLog.e(
                    "Ignoring spurious interrupt of CacheDispatcher thread; "
                            + "use quit() to terminate it");
        }
    }
}

它也是在不斷地調用 processRequest 方法:

private void processRequest() throws InterruptedException {
    // Get a request from the cache triage queue, blocking until
    // at least one is available.
    final Request<?> request = mCacheQueue.take();
    processRequest(request);
}

這裏與 NetworkDispatcher 中一樣,從 mCacheQueue 中取出待請求的 Request,之後看到 processRequest(Request)

void processRequest(final Request<?> request) throws InterruptedException {
    request.addMarker("cache-queue-take");
    // 發送 Event
    request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED);
    try {
        // 若 request 已取消,直接 finish 它
        if (request.isCanceled()) {
            request.finish("cache-discard-canceled");
            return;
        }
        // 通過 request 嘗試獲取 Entry
        Cache.Entry entry = mCache.get(request.getCacheKey());
        if (entry == null) {
        		// 若緩存未命中,則將其放入 mNetworkQueue 等待進行網絡請求
            request.addMarker("cache-miss");
            // Cache miss; send off to the network dispatcher.
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }
        // 如果緩存過期,放入 mNetworkQueue 等待進行網絡請求
        if (entry.isExpired()) {
            request.addMarker("cache-hit-expired");
            request.setCacheEntry(entry);
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }
        // 緩存命中,轉換並獲取 Response
        request.addMarker("cache-hit");
        Response<?> response =
                request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
        request.addMarker("cache-hit-parsed");
        if (!entry.refreshNeeded()) {
            // 如果得到的 response 不需要刷新,直接交付 Response
            mDelivery.postResponse(request, response);
        } else {
            // 如果緩存到期,則將當前 response 設爲中間 Response,進行 Response 交付
            // 當 Response 成功交付,則會將其放入 mNetworkQueue 等待網絡請求
            request.addMarker("cache-hit-refresh-needed");
            request.setCacheEntry(entry);
            // Mark the response as intermediate.
            response.intermediate = true;
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                // 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) {
                                    Thread.currentThread().interrupt();
                                }
                            }
                        });
            } else {
                // request has been added to list of waiting requests
                // to receive the network response from the first request once it returns.
                mDelivery.postResponse(request, response);
            }
        }
    } finally {
        request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED);
    }
}

代碼比較長,它主要有以下的步驟:

  1. Request 已取消,直接 finish 它。
  2. 通過 Request 嘗試獲取緩存的 Response 對應的 Entry
  3. 若緩存未命中,則將其放入 mNetworkQueue 等待進行網絡請求。
  4. 若緩存過期,則將其放入 mNetworkQueue 等待進行網絡請求。
  5. 若緩存命中,轉換並獲取 Response
  6. 若得到的 Response 不需要刷新,直接交付 Response
  7. 若檢測 Response 發現緩存到期,則將當前 Response 設爲中間 Response,進行 Response 交付。此時傳入了一個 Runnable,當 Response 成功交付時,會將 Request放入 mNetworkQueue 等待網絡請求。

簡單點說就是,如果緩存命中且 Response 未過期,則直接將其交付,否則將其放入 mNetworkQueue 等待網絡請求。

Request

前面提到的很多機制都與 Request 有關,爲了更好的理解,我們暫時先不關心 Network 是如何發起網絡請求的,讓我們先看看 Request 的組成。

Request 只是一個抽象類,它的幾個實現如下:

  • StringRequest:返回 StringRequest
  • JsonRequestJsonObjectRequestJsonArrayRequest 的基類。
  • JsonObjectRequest:返回 JsonObjectRequest
  • JsonArrayRequest:返回 JsonArrayRequest
  • ImageRequest:返回圖片的 Request
  • ClearCacheRequest:一個用來清空緩存的 Request,並不是用來進行網絡請求的 Request

構建

要構建一個 Request,我們可以通過其構造函數,其中不同的子類有不同的構造函數,主要的不同是體現在其傳入的 Listener 的不同,例如 StringRequest 的構造函數如下:

public StringRequest(int method, String url, Listener<String> listener, @Nullable ErrorListener errorListener) {
    super(method, url, errorListener);
    mListener = listener;
}

它們都會調用 Request(method, url, errorListener) 的構造函數:

public Request(int method, String url, @Nullable Response.ErrorListener listener) {
    mMethod = method;
    mUrl = url;
    mErrorListener = listener;
    setRetryPolicy(new DefaultRetryPolicy());
    mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
}

如果想要創建一個 POST 請求並傳入參數,需要重寫它的 getParams 方法:

protected Map<String, String> getParams() throws AuthFailureError {
    return null;
}

取消

取消一個 Request 十分簡單,只需要將 mCanceled 置爲 true 即可:

public void cancel() {
    synchronized (mLock) {
        mCanceled = true;
        mErrorListener = null;
    }
}

Response 轉換

RequestparseNetworkResponse 方法是一個抽象方法,需要不同子類去具體實現。

例如 StringRequest 的實現如下:

@Override
@SuppressWarnings("DefaultCharset")
protected Response<String> parseNetworkResponse(NetworkResponse response) {
    String parsed;
    try {
        parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
    } catch (UnsupportedEncodingException e) {
        // Since minSdkVersion = 8, we can't call
        // new String(response.data, Charset.defaultCharset())
        // So suppress the warning instead.
        parsed = new String(response.data);
    }
    return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}

非常簡單,根據 Response.data 轉化爲了對應的 String

Response 的交付

Response 的交付方法 deliverResponse 是一個抽象方法,需要不同子類去具體實現,它主要做的事就是調用 ListeneronResponse 方法,並傳入轉換後的對象。

例如 StringRequest 的實現如下:

@Override
protected void deliverResponse(String response) {
    Response.Listener<String> listener;
    synchronized (mLock) {
        listener = mListener;
    }
    if (listener != null) {
        listener.onResponse(response);
    }
}

Error 的交付

Error 的交付在 Request 中進行了實現,也非常簡單,實際上就是調用 ErrorListeneronErrorResponse 方法:

public void deliverError(VolleyError error) {
    Response.ErrorListener listener;
    synchronized (mLock) {
        listener = mErrorListener;
    }
    if (listener != null) {
        listener.onErrorResponse(error);
    }
}

結束

/**
 * Notifies the request queue that this request has finished (successfully or with error).
 *
 * <p>Also dumps all events from this request's event log; for debugging.
 */
void finish(final String tag) {
    if (mRequestQueue != null) {
        mRequestQueue.finish(this);
    }
    if (MarkerLog.ENABLED) {
        final long threadId = Thread.currentThread().getId();
        if (Looper.myLooper() != Looper.getMainLooper()) {
            // If we finish marking off of the main thread, we need to
            // actually do it on the main thread to ensure correct ordering.
            Handler mainThread = new Handler(Looper.getMainLooper());
            mainThread.post(
                    new Runnable() {
                        @Override
                        public void run() {
                            mEventLog.add(tag, threadId);
                            mEventLog.finish(Request.this.toString());
                        }
                    });
            return;
        }
        mEventLog.add(tag, threadId);
        mEventLog.finish(this.toString());
    }
}

主要是調用了 RequestQueue.finish 方法,它會將當前 Request 從正在執行的 Request 隊列移除。

緩存

Response 被緩存之前會調用 Request.shouldCache 判斷是否需要緩存,

/** Whether or not responses to this request should be cached. */
// TODO(#190): Turn this off by default for anything other than GET requests.
private boolean mShouldCache = true;

可以看到,除了 GET 請求,其餘請求方式都不允許緩存。

我們再看看存在緩存中的 key 是如何通過 Request 轉換而得:

public String getCacheKey() {
    String url = getUrl();
    // If this is a GET request, just use the URL as the key.
    // For callers using DEPRECATED_GET_OR_POST, we assume the method is GET, which matches
    // legacy behavior where all methods had the same cache key. We can't determine which method
    // will be used because doing so requires calling getPostBody() which is expensive and may
    // throw AuthFailureError.
    // TODO(#190): Remove support for non-GET methods.
    int method = getMethod();
    if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) {
        return url;
    }
    return Integer.toString(method) + '-' + url;
}

由於新版 Volley 中只支持 GET 請求進行緩存,因此 Request 是以 url 作爲緩存的 key

Event 機制

我們暫時先不關心 Network 的具體實現,讓我們先看看 Event 發出後做了什麼。

調用 sendEvent 後,轉調到了 RequestQueue.sendRequestEvent 中:

void sendRequestEvent(Request<?> request, @RequestEvent int event) {
    synchronized (mEventListeners) {
        for (RequestEventListener listener : mEventListeners) {
            listener.onRequestEvent(request, event);
        }
    }
}

可以看出,我們的用戶可以向 RequestQueue 中註冊一個 RequestEventListener 來監聽 Request 相關的 Event

Response

相比 RequestResponse 就簡單了很多(感覺很多 Response 的功能放在了 Request),裏面主要是攜帶了一些請求結束後的相關信息。

public class Response<T> {

    /** Callback interface for delivering parsed responses. */
    public interface Listener<T> {
        /** Called when a response is received. */
        void onResponse(T response);
    }

    /** Callback interface for delivering error responses. */
    public interface ErrorListener {
        /**
         * Callback method that an error has been occurred with the provided error code and optional
         * user-readable message.
         */
        void onErrorResponse(VolleyError error);
    }

    /** Returns a successful response containing the parsed result. */
    public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
        return new Response<>(result, cacheEntry);
    }

    /**
     * Returns a failed response containing the given error code and an optional localized message
     * displayed to the user.
     */
    public static <T> Response<T> error(VolleyError error) {
        return new Response<>(error);
    }

    /** Parsed response, or null in the case of error. */
    public final T result;

    /** Cache metadata for this response, or null in the case of error. */
    public final Cache.Entry cacheEntry;

    /** Detailed error information if <code>errorCode != OK</code>. */
    public final VolleyError error;

    /** True if this response was a soft-expired one and a second one MAY be coming. */
    public boolean intermediate = false;

    /** Returns whether this response is considered successful. */
    public boolean isSuccess() {
        return error == null;
    }

    private Response(T result, Cache.Entry cacheEntry) {
        this.result = result;
        this.cacheEntry = cacheEntry;
        this.error = null;
    }

    private Response(VolleyError error) {
        this.result = null;
        this.cacheEntry = null;
        this.error = error;
    }
}

Network

從前面的分析中,我們可以知道,真正的網絡請求是通過 Network 類實現的,它是一個抽象類,只有一個子類 BaseNetwork

我們主要關注它的 performRequest 方法:

@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
    long requestStart = SystemClock.elapsedRealtime();
    while (true) {
    		// 不斷循環
        HttpResponse httpResponse = null;
        byte[] responseContents = null;
        List<Header> responseHeaders = Collections.emptyList();
        try {
            // 獲取 Header
            Map<String, String> additionalRequestHeaders =
                    getCacheHeaders(request.getCacheEntry());
						// 通過 httpStack.executeRequest 來同步執行網絡請求
            httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
            int statusCode = httpResponse.getStatusCode();
            responseHeaders = httpResponse.getHeaders();
            // 若沒有 Modified,則構建一個 NetworkResponse 並返回
            if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                Entry entry = request.getCacheEntry();
                if (entry == null) {
                    return new NetworkResponse(
                            HttpURLConnection.HTTP_NOT_MODIFIED,
                            /* data= */ null,
                            /* notModified= */ true,
                            SystemClock.elapsedRealtime() - requestStart,
                            responseHeaders);
                }
                // 將 response 的 header 和緩存的 entry 結合,變成新的 Headers
                List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
                return new NetworkResponse(
                        HttpURLConnection.HTTP_NOT_MODIFIED,
                        entry.data,
                        /* notModified= */ true,
                        SystemClock.elapsedRealtime() - requestStart,
                        combinedHeaders);
            }
            // 有的 Response 沒有 content,因此需要對 content 進行判斷
            InputStream inputStream = httpResponse.getContent();
            if (inputStream != null) {
                responseContents =
                        inputStreamToBytes(inputStream, httpResponse.getContentLength());
            } 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, statusCode);
            if (statusCode < 200 || statusCode > 299) {
                throw new IOException();
            }
            // 返回請求結果構建的 Response
            return new NetworkResponse(
                    statusCode,
                    responseContents,
                    /* notModified= */ false,
                    SystemClock.elapsedRealtime() - requestStart,
                    responseHeaders);
        } catch (SocketTimeoutException e) {
            attemptRetryOnException("socket", request, new TimeoutError());
        } catch (MalformedURLException e) {
            throw new RuntimeException("Bad URL " + request.getUrl(), e);
        } catch (IOException e) {
            int statusCode;
            if (httpResponse != null) {
                statusCode = httpResponse.getStatusCode();
            } else {
                throw new NoConnectionError(e);
            }
            VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
            NetworkResponse networkResponse;
            if (responseContents != null) {
                networkResponse =
                        new NetworkResponse(
                                statusCode,
                                responseContents,
                                /* notModified= */ false,
                                SystemClock.elapsedRealtime() - requestStart,
                                responseHeaders);
                if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED
                        || statusCode == HttpURLConnection.HTTP_FORBIDDEN) {
                    attemptRetryOnException(
                            "auth", request, new AuthFailureError(networkResponse));
                } else if (statusCode >= 400 && statusCode <= 499) {
                    // Don't retry other client errors.
                    throw new ClientError(networkResponse);
                } else if (statusCode >= 500 && statusCode <= 599) {
                    if (request.shouldRetryServerErrors()) {
                        attemptRetryOnException(
                                "server", request, new ServerError(networkResponse));
                    } else {
                        throw new ServerError(networkResponse);
                    }
                } else {
                    // 3xx? No reason to retry.
                    throw new ServerError(networkResponse);
                }
            } else {
                attemptRetryOnException("network", request, new NetworkError());
            }
        }
    }
}

這裏的代碼非常長,主要是下面的步驟:

  1. 不斷循環進行請求,直到出現錯誤或請求成功
  2. 獲取 Headers
  3. 之後通過 mBaseHttpStack 進行網絡請求
  4. 如果請求結果沒有 Modified,則將返回的 Headers 與緩存的信息結合構建 NetworkResponse 並返回。
  5. 最後如果請求成功,構建 NetworkResponse 並返回。
  6. 若出現錯誤,如果是可以進行重試的(如 3xx 狀態碼或超時),會嘗試進行重試。
  7. 否則會根據具體錯誤構建對應的 ErrorResponse 並返回。

HttpStack

HttpStack 是我們真正執行網絡請求的類,它有兩個實現:HurlStack 以及 HttpClientStack,前者基於 HttpUrlConnection 實現,後者基於 HttpClient 實現。

由於 HttpClient 已經被徹底拋棄,並且目前幾乎已經不存在 SDK 9 以下的機器,因此我們只需要分析 HurlStack 即可,我們看到其 executeRequest 方法:

@Override
public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError {
    String url = request.getUrl();
    HashMap<String, String> map = new HashMap<>();
    map.putAll(additionalHeaders);
    // Request.getHeaders() takes precedence over the given additional (cache) headers).
    map.putAll(request.getHeaders());
    if (mUrlRewriter != null) {
        String rewritten = mUrlRewriter.rewriteUrl(url);
        if (rewritten == null) {
            throw new IOException("URL blocked by rewriter: " + url);
        }
        url = rewritten;
    }
    URL parsedUrl = new URL(url);
    HttpURLConnection connection = openConnection(parsedUrl, request);
    boolean keepConnectionOpen = false;
    try {
        for (String headerName : map.keySet()) {
            connection.setRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        int responseCode = connection.getResponseCode();
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        if (!hasResponseBody(request.getMethod(), responseCode)) {
            return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));
        }
        // Need to keep the connection open until the stream is consumed by the caller. Wrap the
        // stream such that close() will disconnect the connection.
        keepConnectionOpen = true;
        return new HttpResponse(
                responseCode,
                convertHeaders(connection.getHeaderFields()),
                connection.getContentLength(),
                new UrlConnectionInputStream(connection));
    } finally {
        if (!keepConnectionOpen) {
            connection.disconnect();
        }
    }
}

這裏沒什麼難度,就不再詳細介紹,主要是調用了 HttpUrlConnection 的 API 進行網絡請求。

緩存機制

Volley 的緩存機制基於 Cache 這個接口實現,它對外暴露了 putget 等常見的緩存操作接口。默認情況下采用基於磁盤的緩存 DiskBasedCache

這一塊主要是一些對文件的讀取與寫入,暫時就不研究了,有興趣的讀者可以自行閱讀。

總結

爲了在 Application 調用 newRequestQueue 同時又不被 StrictMode 有關文件操作相關的規則所影響,Volley 中使用了一個 FileSupplier 來對 File 進行包裝,它採用了一個懶創建的思路,只有用到的時候才創建對應的 cacheDir 文件

RequestQuque 維護了三個隊列,分別是待請求緩存隊列待網絡請求隊列以及正在請求隊列

RequestQueue 默認情況下維護了 4 個 NetworkDispatcher 以及 1 個 CacheDispatcher,它們都是繼承自 Thread,通過異步進行網絡請求的方式從而提高請求效率。

請求的成功或失敗都會通過 ExecutorDelivery 進行交付,然後通過 Request 通知到各個 Listener

存在一套緩存機制,以 Request 爲 Key,以 Response 爲 Value。只有 GET 請求需要進行緩存,所有 GET 請求會首先被放入緩存隊列 mCacheQueue,當緩存未命中或緩存過期時,纔會被放入 mNetQueue 進行網絡請求

Network 是一個對網絡請求的執行進行包裝的類,它主要負責了 Response 的轉換以及對重試的支持。

HttpStack 是真正執行網絡請求的類,它在高於 SDK 9 的版本下會基於 HttpUrlConnection 進行網絡請求,否則會基於 HttpClient 進行網絡請求。

Cache 實現了 Volley 的緩存機制,默認情況下采用基於磁盤的緩存 DiskBasedCache

Volley 雖沒有像 OkHttp 那樣靈活的攔截器機制,但提供了很多 Listener 供外界對請求過程進行監聽。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章