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

你好,我是 N0tExpectErr0r,一名熱愛技術的 Android 開發html

個人我的博客:blog.N0tExpectErr0r.cnandroid

本篇源碼解析基於 Volley 1.1.1git

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

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) 方法:bash

/**
 * 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 對象。app

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 供外界對請求過程進行監聽。

相關文章
相關標籤/搜索