Android Volley 原始碼解析(一),網路請求的執行流程

developerHaoz發表於2018-02-11

前言

花了好幾天,重新研究了 Volley 的原始碼實現,比起之前又有了一番新的體會,啃原始碼真的是一件讓人糾結的事情,閱讀優秀的原始碼,特別是難度相對較大的原始碼,一旦陷入程式碼細節或者情緒一煩躁,很容易讓人奔潰,但是真正的啃下來,收穫真的很大。從優秀的程式碼中學習優秀的程式設計思想以及良好的程式碼設計和程式碼風格是一個非常好的方法,這次通讀了 Volley 的原始碼之後,對於 Volley 的程式碼質量和擴充性深感佩服,為了更好的記錄這次的原始碼研究之旅,寫幾篇部落格記錄一下。

一、Volley 簡介


Volley 是 Google 在 2013 年的 I/O 大會上推出的 「Android 非同步網路請求框架和圖片載入框架」,它的設計目標就是去進行 資料量不大,但 通訊頻繁 的網路操作,而對於大資料量的網路操作,比如下載檔案等,Volley 的表現就會非常糟糕。

Volley 的使用方法

在進行原始碼分析之前,先讓我們來看下平時是怎樣使用 Volley 的

   RequestQueue requestQueue = Volley.newRequestQueue(context);
   StringRequest stringRequest = new StringRequest(url
           , new Response.Listener<String>() {
       @Override
       public void onResponse(String s) {
           // TODO:
       }
   }, new Response.ErrorListener() {
       @Override
       public void onErrorResponse(VolleyError error) {
          // TODO:
       }
   });
   requestQueue.add(stringRequest);
複製程式碼

1、通過 Volley.newRequestQueue(Context) 獲取一個 RequestQueue 2、傳入 URL 構建 Request,並實現相應的回撥 3、將 Request 加入到 RequestQueue 中

Volley 中比較重要的類

在這先把 Volley 中比較重要的類說一下,到時候看原始碼能更加明白:

類名 作用
Volley 對外暴露的 API,主要作用是構建 RequestQueue
Request 所有網路請求的抽象類,StringRequest、JsonRequest、ImageRequest 都是它的子類
RequestQueue 存放請求的佇列,裡面包括 CacheDispatcher、NetworkDispatcher 和 ResponseDelivery
Response 封裝一個解析後的結果以便分發
CacheDispatcher 用於執行快取佇列請求的執行緒
NetworkDispatcher 使用者執行網路佇列請求的執行緒
Cache 快取請求結果,Volley 預設使用的是基於 sdcard 的 DiskBaseCache
HttpStack 處理 Http 請求,並返回請求結果
Network 呼叫 HttpStack 處理請求,並將結果轉換成可被 ResponseDelivery 處理的 NetworkResponse
ResponseDelivery 返回結果的分發介面

二、請求的執行流程


我們從 Volley 的使用方法入手,一步一步探究底層的原始碼實現,我們的入手點就是 Volley.newRequestQueue(context)

    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, (BaseHttpStack) null);
    }
複製程式碼

這個方法只有一行程式碼,只是呼叫了 newRequestQueue() 的方法過載,並給第二個引數傳入 null,那我們看下帶有兩個引數的 newRequestQueue 方法中的程式碼

    public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
        BasicNetwork network;
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                network = new BasicNetwork(new HurlStack());
            } else {
                String userAgent = "volley/0";
                try {
                    String packageName = context.getPackageName();
                    PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 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);
    }

    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;
    }
複製程式碼

可以看到,這個方法中先判斷 stack 是否為 null,如果是的話,這裡會根據 Android 手機的系統版本號來進行相應的處理,當 SDK >= 9,則建立一個 HurlStack 例項,否則建立一個 HttpClientStack 例項,實際上 HurlStack 內部使用的是 HttpURLConnction 進行網路請求,而 HttpClientStack 則是使用 HttpClient 進行網路請求,這裡之所以要這麼處理,主要是因為在 Android 2.3(SDK = 9)之前,HttpURLConnection 存在一個很嚴重的問題,所以這時候用 HttpClient 來進行網路請求會比較合適,具體的原因可以看下這篇文章:Android 一起來看看 HttpURLConnection 和 HttpClient 的區別

不過由於現在的 Android 手機基本都是 4.0 以上的,而且 HttpClient 已經由於某些原因被棄用了,所以現在只要瞭解 HttpURLConnection 相關的知識就夠了。思路拉回來,我們繼續看程式碼,拿到 Stack 的例項之後將其構建成一個 Network 物件,它是用於根據傳入的 Stack 物件來處理網路請求的,緊接著構建出一個 RequestQueue 物件,並呼叫 start() 方法。

我們接著看 start() 方法究竟做了什麼:

    public void start() {
        stop();
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

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

先呼叫 stop() 方法將當前正在進行 Dispatcher 都停掉,然後建立了一個 CacheDispatcher 例項,並呼叫了它的 start() 方法,接著在一個迴圈裡去建立 NetworkDispatcher 的例項,分別呼叫它們的 start() 方法,這裡的 CacheDispatcher 和 NetworkDispatcher 都是繼承自 Thread 的,預設情況下 for 迴圈會執行四次,也就是說當呼叫了 Volley.newRequestQueue(context) 之後,就會有五個執行緒在後臺執行,等待網路請求的到來,其中 CacheDispatcher 是快取執行緒,NetworkDispatcher 是網路請求執行緒。

得到 RequestQueue 之後,構建相應的 Request,然後呼叫 add() 方法將其加入到請求佇列中

    public <T> Request<T> add(Request<T> request) {
        // 將 Request 標記為屬於此佇列,並將其放入 mCurrentRequests 中
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // 讓 Request 按照他們被新增的順序執行
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        //如果請求不需要被快取,就跳過快取,直接進行網路請求
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
        mCacheQueue.add(request);
        return request;
     }
複製程式碼

可以看到,傳入 Request 之後,會先判斷該 Request 是否需要進行快取,如果不需要就直接將其加入到網路請求佇列,需要快取則加入快取佇列。預設情況下,每條請求都是應該快取的,當然我們也可以呼叫 Request 的 setShouldCache() 方法來進行設定。

Request 被新增到快取佇列中後,在後臺等待的快取執行緒就要開始執行起來了,我們看下 CacheDispatcher 的 run() 方法究竟是怎麼實現的。

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // 初始化 Cache
        mCache.initialize();

        while (true) {
            try {
                processRequest();
            } catch (InterruptedException e) {
                if (mQuit) {
                    return;
                }
            }
        }
    }

    private void processRequest() throws InterruptedException {
        final Request<?> request = mCacheQueue.take();
        request.addMarker("cache-queue-take");

        // 如果請求已經取消了,我們直接結束該請求
        if (request.isCanceled()) {
            request.finish("cache-discard-canceled");
            return;
        }

        // 從 Cache 中取出包含請求快取資料的 Entry
        Cache.Entry entry = mCache.get(request.getCacheKey());
        if (entry == null) {
            request.addMarker("cache-miss");
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

        // 如果快取的請求過期了,就將其新增到網路請求佇列中
        if (entry.isExpired()) {
            request.addMarker("cache-hit-expired");
            request.setCacheEntry(entry);
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

        // 快取的資料封裝成 NetworkResponse
        Response<?> response = request.parseNetworkResponse(
                new NetworkResponse(entry.data, entry.responseHeaders));

        if (!entry.refreshNeeded()) {
            // 如果快取沒有過期就直接進行分發
            mDelivery.postResponse(request, response);
        } else {
            // 重置該請求的 Entry
            request.setCacheEntry(entry);
            response.intermediate = true;

            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mDelivery.postResponse(request, response, new Runnable() {
                    @Override
                    public void run() {
                        try {
                            mNetworkQueue.put(request);
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                });
            } else {
                mDelivery.postResponse(request, response);
            }
        }
    }
複製程式碼

程式碼相對比較長,我在關鍵的地方已經打上註釋了,在這裡總結一下,可以看到在初始化了 Cache 之後,有一個 while(true) 迴圈,說明快取執行緒是始終執行的,接著會在快取中取出響應結果,如果為 null 的話,就將其加入到網路請求佇列中,如果不為空的話,再判斷該快取是否已過期,已經過期則同樣把這條請求加入到網路請求佇列中,否則直接使用快取中的資料。最後將資料進行解析,並進行分發。

看完 CacheDispathcer 的 run() 方法,我們接著看 NetworkDispatcher 的 run() 方法

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            try {
                processRequest();
            } catch (InterruptedException e) {
                if (mQuit) {
                    return;
                }
            }
        }
    }

    private void processRequest() throws InterruptedException {
        Request<?> request = mQueue.take();

        long startTimeMs = SystemClock.elapsedRealtime();
        try {
            request.addMarker("network-queue-take");

            // 如果 Request 已經取消了,那就不執行網路請求
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                request.notifyListenerResponseNotUsable();
                return;
            }

            addTrafficStatsTag(request);

            // 執行網路請求
            NetworkResponse networkResponse = mNetwork.performRequest(request);

            // 如果伺服器返回 304,而且我們已經分發過該 Request 的結果,那就不用進行第二次分發了
            //(這裡補充一下,304 代表伺服器上的結果跟上次訪問的結果是一樣的,也就是說資料沒有變化)
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                request.notifyListenerResponseNotUsable();
                return;
            }

            // 在子執行緒解析返回的結果
            Response<?> response = request.parseNetworkResponse(networkResponse);

            // 如果需要的話,就將返回結果寫入快取
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);            }

            // 分發響應結果
            request.markDelivered();
            mDelivery.postResponse(request, response);
            request.notifyListenerResponseReceived(response);
        } catch (VolleyError volleyError) {
            request.notifyListenerResponseNotUsable();
        } catch (Exception e) {
            request.notifyListenerResponseNotUsable();
        }
    }
複製程式碼

在 NetworkDispatcher 同樣使用了 while(true),說明網路請求執行緒也是不斷執行的。然後從網路佇列裡面取出 Request,再呼叫 Network 的 performRequest() 方法去傳送網路請求。Network 其實是一個介面,這裡具體的實現是 BasicNetwork,我們來看下它的 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 {
                Map<String, String> additionalRequestHeaders =
                        getCacheHeaders(request.getCacheEntry());
                 // 該注意的地方:呼叫 Stack 的 executeRequest 進行網路請求
                httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                int statusCode = httpResponse.getStatusCode();

                responseHeaders = httpResponse.getHeaders();
                if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, null, true,
                                SystemClock.elapsedRealtime() - requestStart, responseHeaders);
                    }
                    List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
                    return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, entry.data,
                            true, SystemClock.elapsedRealtime() - requestStart, combinedHeaders);
                }

                // 有些返回結果是沒有內容的,如:204,所以我們必須進行檢查
                InputStream inputStream = httpResponse.getContent();
                if (inputStream != null) {
                  responseContents =
                          inputStreamToBytes(inputStream, httpResponse.getContentLength());
                } else {
                  responseContents = new byte[0];
                }
                return new NetworkResponse(statusCode, responseContents, false,
                        SystemClock.elapsedRealtime() - requestStart, responseHeaders);
            } catch (Exception e) {
                // ...
            }
        }
    }
複製程式碼

這個方法裡面,基本上都是網路請求方面處理的細節,我們這篇文章,主要是梳理整體的流程,對細節方面先不深入。需要注意的是在我標註的第一個地方,呼叫了 Stack 的 executeRequest() 方法,這裡的 Stack 就是之前呼叫 Volley.newRequestQueue() 所建立的例項,前面也說過了這個物件的內部是使用了 HttpURLConnection 或 HttpClient(已棄用)來進行網路請求。網路請求結束後將返回的資料封裝成一個 NetworkResponse 物件進行返回。

在 NetworkDispatcher 接收到了這個 NetworkResponse 物件之後,又會呼叫 Request 的 parseNetworkResponse() 方法來對結果進行解析,然後將資料寫入到快取,最後呼叫 ExecutorDelivery 的 postResponse() 方法來回撥解析後的資料,如下所示:

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

在 mResponsePoster(一個 Executor 的例項物件) 的 execute() 方法中傳入了一個 ResponseDeliveryRunnable 物件,execute() 方法預設是在主執行緒中執行的,這樣就保證了 ResponseDeliveryRunnable 的 run() 方法也是在主執行緒當中執行的,我們看下 run() 方法裡面的邏輯:

        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            // 如果 Request 被取消了,呼叫 finish() 方法,結束該請求,不進行傳遞
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // 根據響應的結果來進行不同的分發
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }

            // 如果傳入的 mRunnable 不為 null,則執行
            if (mRunnable != null) {
                mRunnable.run();
            }
       }
複製程式碼

可以看到當 Response.isSuccess() 為 true 的話,呼叫 Resquest 的 deliverResponse() 方法,對結果進行回撥,deliverResponse() 方法是每一個具體的 Request 子類都必須實現的抽象類,來看下我們最熟悉的 StringRequest 中的 deliverResponse() 方法

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

看到這裡應該就很明白了,在 deliverResponse() 方法中,呼叫 listener.onResponse() 方法進行回撥,這個 listener 正是我們構建 StringRequest 時傳入的 Listener,也就是說將返回的結果回撥到我們在外部呼叫的地方。

   StringRequest stringRequest = new StringRequest(url
           , new Response.Listener<String>() {
       @Override
       public void onResponse(String s) {
           // TODO:
       }
   }, new Response.ErrorListener() {
       @Override
       public void onErrorResponse(VolleyError error) {
          // TODO:
       }
   });
複製程式碼

到這裡,終於把 Volley 的完整執行流程全部都梳理了一遍,最後我們來看一下 Volley 官方提供的流程圖:

Android Volley 原始碼解析(一),網路請求的執行流程


參考

相關文章