Android源碼系列三:Volley源碼剖析

Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster.

Volley是Google在2013年推出來的HTTP庫,旨在幫助開發者更快更簡便的實現網絡請求。說說爲什麼要分析Volley的源碼吧,因爲Volley中線程的轉換時通過 ThreadHandler 來實現的,跟之前的兩篇都有着很大的聯繫(ps:Okhttp和Retrofit都撕不動_),哈哈,後面會一步一步的給大家帶來Okhttp和Retrofit等更多的源碼分析!

執行一個網絡請求

我們先整體看下 Volley 是如何進行一個完整的網絡請求的:

val requestQueue: RequestQueue = Volley.newRequestQueue(context)
val url = "https://www.baidu.com"
val request = StringRequest(url,
        Response.Listener<String> {
            Log.d("taonce", "request result is: $it")
        },
        Response.ErrorListener { })

requestQueue.add(request)

上面代碼主要做了三件事:

  • 創建一個請求隊列 RequestQueue : Volley.newRequestQueue(context)
  • 創建一個請求 Request : StringRequest(String url, Listener<String> listener, @Nullable ErrorListener errorListener)
  • Request 加入到 RequestQueue : requestQueue.add(request)

接下來通過源碼的方法來看看這三步內部做了什麼操作。

創建 RequestQueue 和 Request進入 Volley.newRequestQueue(context) 源碼:

進入 Volley.newRequestQueue(context) 源碼:

public static RequestQueue newRequestQueue(Context context) {
    // 實際上是調用了另外一個構造方法
    return newRequestQueue(context, (BaseHttpStack) null);
}

繼續查看 newRequestQueue(Context context, BaseHttpStack stack) :

BasicNetwork network;
if (stack == null) {
    // 判斷是否大於等於 Android 2.3 版本
    if (Build.VERSION.SDK_INT >= 9) {
        // 如果是 Android 2.3 及其以上,就用 HurlStack() 進行網絡請求
        network = new BasicNetwork(new HurlStack());
    } else {
        // 如果是 Android 2.3 以下,那麼就採用 HttpClientStack(HttpClient) 進行網絡請求
        network = new BasicNetwork(
                        new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
    }
} else {
    // 如果stack不爲空,那麼就採用傳進來的stack
    network = new BasicNetwork(stack);
}

return newRequestQueue(context, network);

可以得出:

  • 創建一個 BasicNetwork 對象
  • stack 爲空 ----> Android 2.3 及其以上創建 HurlStack() 對象,並且傳給 networkHurlStack() 採用的是 HttpURLConnetion 進行網絡請求的。
  • stack 爲空 ----> Android 2.3 以下創建 HttpClientStack() 對象,並且傳給 networkHttpClientStack() 採用的則是 HttpClient 進行網絡請求,不過現在( 當前版本1.1.1 ) new HttpClientStack(HttpClient client) 已經被標記了 @Deprecated 了,因爲它採用的 HttpClientGoogle 在 Android 6.0 中移除了對 Apache HTTP 客戶端的支持,並且從 Android P 開始,org.apache.legacy 庫將從 bootclasspath 中刪除。
  • stack 不爲空 ----> 直接將 stack 傳給 network
  • 創建一個 RequestQueue() 對象並返回

到此爲止,大家只要記住上面幾個對象就好,接下來會慢慢的講解對象分別做了什麼工作。

這裏還是要着重介紹下 newRequestQueue() 方法:

private static RequestQueue newRequestQueue(Context context, Network network) {
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
    // 創建請求隊列
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();
    return queue;
}

創建 RequestQueue() 的過程中有一個很重要的點,我們來看看它的構造方法:

public RequestQueue(Cache cache, Network network) {
    // 默認 Network Thread 數目爲 DEFAULT_NETWORK_THREAD_POOL_SIZE = 4
    this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(
            cache,
            network,
            threadPoolSize,
            // 注意點就是這:創建 Handler 的時候,傳遞的是 Looper.getMainLooper(),也就是後面將請求結果回調到主線程的關鍵。
            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;
}

上面第二個構造方法中創建了一個 ExecutorDelivery() , 這個對象實現了 ResponseDelivery 接口的類,用來將網絡請求的結果或者緩存中的結果分發到主線程。

再來看看上面 queue.start() 方法做了什麼操作:

// 開啓5個線程
public void start() {
    // 如果5個線程不爲空,先停止它們
    stop(); 
    // 創建 CacheDispatcher,並開啓它
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();

    // 創建4個NetworkDispatcher,並開啓它們
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher =
                new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

到此,我們前奏分析完了,有一些概念先不急着看,只要知道它的作用就行,後面我們來一個一個擊破。

添加請求到請求隊列中 : RequestQueue.add( request )

廢話不多了,直接進入源碼:RequestQueue.add(request)

public <T> Request<T> add(Request<T> request) {
    // 將request和當前的RequestQueue綁定
    request.setRequestQueue(this);
    // 將request添加到set集合中,用於執行 cancelAll() 操作
    synchronized (mCurrentRequests) {
        mCurrentRequests.add(request);
    }

    // 給request添加一些標記
    request.setSequence(getSequenceNumber());
    request.addMarker("add-to-queue");
    sendRequestEvent(request, RequestEvent.REQUEST_QUEUED);

    // 判斷request是否需要緩存,對於 Get 以外的請求,默認關閉緩存  
    if (!request.shouldCache()) {
        mNetworkQueue.add(request);
        return request;
    }
    mCacheQueue.add(request);
    return request;
}

添加 request 的操作算是很簡單的了,無非是根據 request 是否需要緩存,將 request 添加到 CacheQueue 或者 NetworkQueue 中。

看到這,我們是不是在想,怎麼添加之後就沒有動作了?非也非也,我們回顧下,在創建 requestQueue 的同時,是不是創建了5個子線程並且開啓了它們,它們內部使用的 CacheQueue 或者 NetworkQueue 不就是上面提到的麼。接下來,我們先看看 CacheDispatcher 內部做了什麼。

CacheDispatcher 調度器剖析:

CacheDispatcher 就是一個繼承了 Thread 的類,我們在執行 RequestQueue.start() 的時候創建了它,並開啓了它,現在我們來看看它的 run() 方法:

@Override
public void run() {
    // 設置線程優先級爲後臺線程
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    // 實則是執行了DiskBasedCache的initialize()方法
    mCache.initialize();
    while (true) {
        try {
            processRequest();
        } catch (InterruptedException e) {
            // 中斷當前線程,並且退出死循環,mQuit在調用CacheDispatcher的quit()方法之後會被賦值爲true
            if (mQuit) {
                Thread.currentThread().interrupt();
                return;
            }
        }
    }
}

開了一個死循環,然後調用了 processRequest() , 我們接着看這個方法:

    private void processRequest() throws InterruptedException {
        // 從CacheQueue中取出一個可用的request
        final Request<?> request = mCacheQueue.take();
        processRequest(request);
    }

    @VisibleForTesting
    void processRequest(final Request<?> request) throws InterruptedException {
        request.addMarker("cache-queue-take");
        request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED);

        try {
            //request如果被取消了,就直接返回
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                return;
            }

            Cache.Entry entry = mCache.get(request.getCacheKey());
            // 沒有緩存就把request添加到NetworkQueue中
            if (entry == null) {
                request.addMarker("cache-miss");
                // 沒有緩存,並且等待隊列中也沒有此request,那麼就直接加入到NetworkQueue中
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }

            // 如果緩存過期了,也是一樣把request添加到NetworkQueue中
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }

            // 有緩存並且沒有過期
            request.addMarker("cache-hit");
            // 根據緩存的內容解析
            Response<?> response =
                    request.parseNetworkResponse(
                            new NetworkResponse(entry.data, entry.responseHeaders));
            request.addMarker("cache-hit-parsed");
            // 是否需要更新
            if (!entry.refreshNeeded()) {
                // 不需要更新,直接將結果調度到主線程
                mDelivery.postResponse(request, response);
            } else {
                request.addMarker("cache-hit-refresh-needed");
                request.setCacheEntry(entry);
                response.intermediate = true;
                // 判斷是否有相同緩存鍵的任務在執行
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    // 需要更新結果,先將結果調度到主線程,然後執行new runnable(){}
                    // runnable中就是將request添加到NetworkQueue中,更新一下內容
                    mDelivery.postResponse(
                            request,
                            response,
                            new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        mNetworkQueue.put(request);
                                    } catch (InterruptedException e) {
                                        // Restore the interrupted status
                                        Thread.currentThread().interrupt();
                                    }
                                }
                            });
                } else {
                    // request已經加入到mWaitingRequests中
                    // 直接把結果調度到主線程
                    mDelivery.postResponse(request, response);
                }
            }
        } finally {
            request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED);
        }
    }

我們在 processRequest 中可以看到有一個方法經常出現,那就是 mWaitingRequestManager.maybeAddToWaitingRequests(request) ,它的作用是判斷當前這個 request 是否有存在相同緩存鍵的請求已經處於運行狀態,如果有,那麼就將這個 request 加入到一個等待隊列中,等到相同緩存鍵的請求完成。

總結一下 CacheDispatcher 主要步驟:

  • CacheQueue 中循環取出 request
  • 如果緩存丟失,加入到 NetworkQueue 中;
  • 如果緩存過期,加入到 NetworkQueue 中;
  • 將緩存中的數據解析成 Response 對象;
  • 如果不需要更新,直接將結果回調到主線程,回調操作等介紹完NetworkDispatcher之後一起深入剖析;
  • 如果需要更新,先將結果回調到主線程,然後再將 request 加入到 NetworkQueue 中。

NetworkDispatcher 調度器剖析:

NetworkDispatcherCacheDispatcher 十分類似,都是 Thread 的子類,下面重點看下它的 run() 方法:

@Override
public void run() {
    // 設置線程優先級爲後臺線程
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    while (true) {
        try {
            processRequest();
        } catch (InterruptedException e) {
            // 調用quit()方法之後,mQuit就會被賦值true
            if (mQuit) {
                Thread.currentThread().interrupt();
                return;
            }
        }
    }
}

繼續看 processRequest() 方法:

private void processRequest() throws InterruptedException {
    // 從NetworkQueue中取出request
    Request<?> request = mQueue.take();
    processRequest(request);
}

@VisibleForTesting
void processRequest(Request<?> request) {
    long startTimeMs = SystemClock.elapsedRealtime();
    request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
    try {
        request.addMarker("network-queue-take");

        // 如果request被取消了,那麼就不執行此request
        if (request.isCanceled()) {
            request.finish("network-discard-cancelled");
            request.notifyListenerResponseNotUsable();
            return;
        }

        addTrafficStatsTag(request);

        // 還記得這個mNetwork麼,它就是Volley.newRequestQueue()方法裏的BasicNetwork對象,一會我們來看看mNetwork.performRequest()方法是如何得到NetworkResponse的
        NetworkResponse networkResponse = mNetwork.performRequest(request);
        request.addMarker("network-http-complete");

        // notModified是服務端返回304,hasHadResponseDelivered()是request已經回調過了
        if (networkResponse.notModified && request.hasHadResponseDelivered()) {
            request.finish("not-modified");
            request.notifyListenerResponseNotUsable();
            return;
        }

        // 將NetworkResponse解析成Response對象,在子線程中執行
        Response<?> response = request.parseNetworkResponse(networkResponse);
        request.addMarker("network-parse-complete");

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

        request.markDelivered();
        // 回調結果至主線程
        mDelivery.postResponse(request, response);
        request.notifyListenerResponseReceived(response);
    } 
    // 以下都是處理異常錯誤,然後也需要回調至主線程
    catch (VolleyError volleyError) {
        volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
        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);
        mDelivery.postError(request, volleyError);
        request.notifyListenerResponseNotUsable();
    } finally {
        request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED);
    }
}

通過 NetworkDispatcher.run() 方法可以發現,主要分爲以下幾步:

  • 通過 BasicNetwork.performRequest(request) 得到 NetworkResponse 對象;
  • 通過 request.parseNetworkResponse(networkResponse) 解析得到 Response 對象;
  • 通過 mDelivery 將成功結果或者失敗結果回調到主線程。

現在我們依次來分析下這三步:

  1. 請求網絡,得到 NetworkResponse

    BasicNetwork 實現了 Network 接口,其主要代碼集中在 NetworkResponse performRequest(Request<?> request) throws VolleyError 中,也就是執行特定的請求。

    @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 {
                // 得到請求頭信息
                Map<String, String> additionalRequestHeaders =
                        getCacheHeaders(request.getCacheEntry());
                // 具體的網絡請求是靠BaseHttpStack執行的
                httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                int statusCode = httpResponse.getStatusCode();
    
                responseHeaders = httpResponse.getHeaders();
                // 下面就是根據不同的狀態碼返回不同的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);
                    }
                }
                // 省略大部分代碼...
    }
    

    通過上面源碼可以看出,BasicNetwork 就是封裝了一下 NetworkResponse 對象蠻,並沒有涉及到網絡請求,我們繼續深入到 BaseHttpStack.executeRequest(request, additionalRequestHeaders) 源碼中。

    public abstract class BaseHttpStack implements HttpStack {
        public abstract HttpResponse executeRequest(
                Request<?> request, Map<String, String> additionalHeaders)
                throws IOException, AuthFailureError;
    }
    

    我們發現 BaseHttpStack 是一個抽象類,那麼具體的請求是在哪呢,不知道你是否還有印象,在Volley.newRequestQueue() 中,我們創建 BasicNetwork 的時候是根據 Android 版本傳入不同的 BaseHttpStack 子類,Android 2.3 以上我們傳入的是 HurlStack 對象,下面就看看 HurlStack.executeRequest() 方法吧。

    @Override
    public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        // 得到url
        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());
     ...
        URL parsedUrl = new URL(url);
        // 通過HttpURLConnection來請求網絡
        HttpURLConnection connection = openConnection(parsedUrl, request);
    }
    

    其實 HurlStack.executeRequest() 方法裏,就是藉助了 HttpURLConnection 對象來請求網絡,並根據不同的條件返回不同的 HttpResponse 對象的。

  2. 解析 NetworkResponse , 得到 Response

    解析過程是定義在 Request 抽象類的 protected abstract Response<T> parseNetworkResponse(NetworkResponse response) 抽象方法中,對於每一種具體的 Request 類,比如:StringRequestJsonObjectRequest 等等都有自己的實現,下面我們來看看 StringRequest 中的 parseNetworkResponse() 方法:

    @Override
    @SuppressWarnings("DefaultCharset")
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            // 將response中的data信息取出來
            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);
        }
        // 封裝成Response對象
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
    
  3. 回調主線程,如果你看過我之前的 Handler源碼剖析 文章話,那麼這一步就很簡單了,我們來理一理:

    回調是通過 ResponseDelivery mDelivery 對象來執行的,這個對象最早是在創建 RequestQueue() 的時候初始化的,我在那一小節特意標註了,再來回顧下:

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
            this(
                    cache,
                    network,
                    threadPoolSize,
                 // 創建ExecutorDelivery對象,傳入一個Handler對象,並且Handler綁定的是主線程的Looper
                    new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }
    
    public class ExecutorDelivery implements ResponseDelivery {
        private final Executor mResponsePoster;
        /**
         * ExecutorDelivery構造函數,內部初始化了mResponsePoster接口
         * 並且在execute()方法裏調用了handler.post()方法
         */
        public ExecutorDelivery(final Handler handler) {
            // 初始化Execute對象
            mResponsePoster =
                    new Executor() {
                        @Override
                        public void execute(Runnable command) {
                            handler.post(command);
                        }
                    };
        }
    }
    

    知道了它的初始化,我們再來看看它是如何實現回調的:

    Volley 中回調是通過postResponse()方法的 :

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

    postResponse() 最終會調用 mResponsePoster 對象的 execute() 方法,傳入了一個 ResponseDeliveryRunnable 對象,它實現了 Runnable 接口,execute() 方法會通過 Handler.post(runnable)ResponseDeliveryRunnable 放入消息隊列。最後我們來看看這個 ResponseDeliveryRunnablerun() 方法在主線程中做了什麼操作:

    @SuppressWarnings("unchecked")
    @Override
    public void run() {
    
        // If this request has canceled, finish it and don't deliver.
        if (mRequest.isCanceled()) {
            mRequest.finish("canceled-at-delivery");
            return;
        }
    
        if (mResponse.isSuccess()) {
            // 執行成功的回調,在具體的Request實現類中,比如StringRequest就會調用listener.onResponse(string)回調
            mRequest.deliverResponse(mResponse.result);
        } else {
            // 執行失敗的回調,在request中,直接回調了listener.onErrorResponse(error)
            mRequest.deliverError(mResponse.error);
        }
    
        // intermediate默認爲false,但是在CacheDispatcher的run()中,如果需要更新緩存,那麼就會置爲true
        if (mResponse.intermediate) {
            mRequest.addMarker("intermediate-response");
        } else {
            mRequest.finish("done");
        }
    
        // 如果傳入了runnable不爲空,那就就執行runnable.run()方法
        // 回憶下在CacheDispatcher的run()方法中,如果request有緩存,但是需要更新緩存的時候,mDelivery是不是調用的帶runnable的方法
        if (mRunnable != null) {
            mRunnable.run();
        }
    }
    

    分析到這的時候,大家可以藉助下方的流程圖理一理Volley的整體流程,主要理解一下緩存線程和網絡線程的轉換,子線程和主線程的轉換:


    源碼分析的文字還在不斷的更新,如果本文章你發現的不正確或者不足之處,歡迎你在下方留言或者掃描下方的二維碼留言也可!


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