Volley源碼解析

導(dǎo)語

閱讀源碼是已經(jīng)入門的Android開發(fā)者必經(jīng)之路啡捶,這是提高自己對代碼理解的一步利凑。但是一開始閱讀源碼不能深入的去讀細(xì)節(jié)部分卸勺,而是理順源碼的主要部分砂沛,理解它的實(shí)現(xiàn)原理即可,否則一旦陷入會(huì)導(dǎo)致很難讀懂曙求,可能會(huì)失去閱讀源碼的興趣碍庵。volley現(xiàn)在已經(jīng)不是主流的網(wǎng)絡(luò)請求框架,但是其結(jié)構(gòu)并不復(fù)雜悟狱,容易理解静浴,因此選擇此框架來學(xué)習(xí)。

一挤渐、Volley的使用

RequestQueue requestQueue = 
    Volley.newRequestQueue(context.getApplicationContext());// 創(chuàng)建請求隊(duì)列實(shí)例
StringRequest request = new StringRequest(url,
    new Listener<String>() {
        @Override
        public void onResponse(String response) { // 成功返回?cái)?shù)據(jù)苹享,做UI更新等操作},
    new ErrorListener() {
         @Override
         public void onErrorResponse(VolleyError arg0) {// 失敗,做相應(yīng)處理}
    });
requestQueue.add(request);// 將請求添加至請求隊(duì)列中

以上是volley常規(guī)的使用方式浴麻。先是創(chuàng)建請求隊(duì)列得问,這個(gè)一般在采用單例模式創(chuàng)建然后用于全局;接著創(chuàng)建請求软免,請求有StringRequest宫纬、JsonObjectRequest等等,主要區(qū)別就是返回?cái)?shù)據(jù)格式不同膏萧。最后將請求添加至請求隊(duì)列(循環(huán)處理請求)漓骚。

二、Volley解析

閱讀源碼的方法很簡單榛泛,就是跟進(jìn)代碼认境,一點(diǎn)點(diǎn)理解。我們從創(chuàng)建請求隊(duì)列實(shí)例開始跟進(jìn)挟鸠。(可能會(huì)省略部分不重要的源碼)

2.1 Volley

跟進(jìn)Volley.newRequestQueue(context.getApplicationContext()),可以發(fā)現(xiàn)此類是volley框架的入口亩冬,源碼如下:

    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), "volley");

        if(stack == null) {// 在api9以前使用httpClient網(wǎng)絡(luò)請求艘希,而api9以后使用httpUrlConnection,但是在api23以后已經(jīng)不支持httpClient了
            if(VERSION.SDK_INT >= 9) {
                stack = new HurlStack();// 用httpUrlConnection請求并獲取結(jié)果
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));// 用httpClient請求并獲取結(jié)果
            }
        }

        BasicNetwork network1 = new BasicNetwork((HttpStack)stack);// 處理請求硅急,使用httpStack獲得請求結(jié)果封裝后返回
        RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);// 創(chuàng)建請求隊(duì)列覆享,并傳入緩存對象以及處理請求對象
        queue1.start();// 啟動(dòng)了緩存線程以及網(wǎng)絡(luò)請求線程
        return queue1;
    }

2.2 RequestQueue

我們先跟進(jìn)queue1.start(),讀源碼的時(shí)候不能一直按順序讀营袜,如果先跟進(jìn)BasicNetWork或DiskBasedCache會(huì)很難理解撒顿,但是我們能從語義上基本能看出他們的作用,一個(gè)是網(wǎng)絡(luò)請求一個(gè)是磁盤緩存荚板。queue1.start()能看出是隊(duì)列啟動(dòng)的入口凤壁,從這里進(jìn)入會(huì)遇到使用BasicNetWork或DiskBasedCache這些類的時(shí)候吩屹。

    public void start() {
        this.stop();
        this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue,
             this.mNetworkQueue, this.mCache, this.mDelivery);// 這是一個(gè)緩存線程,已緩存結(jié)果的一些請求不必再去進(jìn)行網(wǎng)絡(luò)請求拧抖,直接在mCacheQueue里拿結(jié)果煤搜,同時(shí)緩存會(huì)有過期、新鮮度等判斷唧席,如果不符合條件則取mNetworkQueue里的網(wǎng)絡(luò)請求擦盾。mCache是緩存結(jié)果的一個(gè)實(shí)例,mDelivery是分發(fā)結(jié)果的實(shí)例淌哟。
        this.mCacheDispatcher.start();

        for(int i = 0; i < this.mDispatchers.length; ++i) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue,
                this.mNetwork, this.mCache, this.mDelivery);// 網(wǎng)絡(luò)請求線程迹卢,網(wǎng)絡(luò)請求時(shí)間長,因此需要多個(gè)線程一起執(zhí)行徒仓,默認(rèn)是4個(gè)腐碱,可以自己設(shè)置。
            this.mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }

    }

2.3 NetworkDispatcher

先講解NetworkDispatcher是因?yàn)槲蚁胍鶕?jù)一開始執(zhí)行的流程走蓬衡, 只有進(jìn)行過網(wǎng)絡(luò)請求的才有結(jié)果緩存喻杈,這樣思路比較順點(diǎn)。NetworkDispatcher是一個(gè)處理網(wǎng)絡(luò)請求的線程狰晚。

public void run() {

        while(true) {
            long startTimeMs;
            Request request;
            while(true) {// 只要網(wǎng)絡(luò)請求隊(duì)列里出現(xiàn)請求筒饰,馬上取出執(zhí)行。
                startTimeMs = SystemClock.elapsedRealtime();
                try {
                    request = (Request)this.mQueue.take();// 從隊(duì)列拿出請求
                    break;
                } catch (InterruptedException var6) {
                    if(this.mQuit) {
                        return;
                    }
                }
            }

            try {
                request.addMarker("network-queue-take");
                if(request.isCanceled()) {// 這些地方我們就不必去深究壁晒,只要語義上理解就好瓷们,很明顯這是請求取消了,然后請求完成秒咐。傳入了一些標(biāo)志的字符串谬晕。
                    request.finish("network-discard-cancelled");
                } else {
                    NetworkResponse e = this.mNetwork.performRequest(request);// 這里就是關(guān)鍵的網(wǎng)絡(luò)請求,拿到封裝好的結(jié)果(其實(shí)是將httpResponse解析成自定義的NetworkResponse)
                    request.addMarker("network-http-complete");
                    if(e.notModified && request.hasHadResponseDelivered()) {
                        request.finish("not-modified");
                    } else {
                        Response volleyError1 = request.parseNetworkResponse(e);// 再次將NetWorkResponse對象轉(zhuǎn)換成自定義的Response對象携取。Response最終可解析成我們需要的數(shù)據(jù)攒钳,而NetworkResonse只是過渡的一個(gè)對象。
                        request.addMarker("network-parse-complete");
                        if(request.shouldCache() && volleyError1.cacheEntry != null) {
                            this.mCache.put(request.getCacheKey(), 
                                volleyError1.cacheEntry);// 這里就是緩存結(jié)果
                            request.addMarker("network-cache-written");
                        }

                        request.markDelivered();
                        this.mDelivery.postResponse(request, volleyError1);// 這也是關(guān)鍵雷滋,將結(jié)果分發(fā)給request不撑,request再傳給監(jiān)聽器回調(diào)給主線程
                    }
                }
            } catch (VolleyError var7) {
                var7.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                this.parseAndDeliverNetworkError(request, var7);
            } catch (Exception var8) {
                VolleyError volleyError = new VolleyError(var8);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                this.mDelivery.postError(request, volleyError);
            }
        }
    }

2.4 BasicNetwork

該類實(shí)現(xiàn)了Network接口,主要的作用是進(jìn)行網(wǎng)絡(luò)請求并返回結(jié)果晤斩。 跟進(jìn)以上代碼的mNetwork.performRequest(request)

    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();

        while(true) {
            HttpResponse httpResponse = null;
            Object responseContents = null;
            Map responseHeaders = Collections.emptyMap();

            try {
                HashMap e = new HashMap();
                this.addCacheHeaders(e, request.getCacheEntry());// 取出request中的緩存實(shí)體焕檬,將部分信息緩存到緩存頭中(可能無法理解為什么要有緩存頭,不用死纏爛打澳泵,先看下面)
                httpResponse = this.mHttpStack.performRequest(request, e);// 這里就是真正的請求數(shù)據(jù)并返回結(jié)果实愚。
                StatusLine statusCode2 = httpResponse.getStatusLine();
                int networkResponse1 = statusCode2.getStatusCode();// 狀態(tài)碼,用于判斷服務(wù)端的結(jié)果是否改變
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());// 解析返回結(jié)果的響應(yīng)頭
                if(networkResponse1 != 304) {// 304表示在一定時(shí)間內(nèi)結(jié)果是否有改動(dòng)
                    byte[] responseContents1;
                    if(httpResponse.getEntity() != null) {
                        responseContents1 = this.entityToBytes(httpResponse.getEntity());// 將httpEntry實(shí)體轉(zhuǎn)換成字節(jié)數(shù)組
                    } else {
                        responseContents1 = new byte[0];
                    }

                    long requestLifetime1 = SystemClock.elapsedRealtime() - requestStart;
                    this.logSlowRequests(requestLifetime1, request, responseContents1, statusCode2);
                    if(networkResponse1 >= 200 && networkResponse1 <= 299) {
                        return new NetworkResponse(networkResponse1, responseContents1, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);// 返回一個(gè)根據(jù)服務(wù)端返回結(jié)果來構(gòu)建的NetworkResponse對象
                    }

                    throw new IOException();
                }

                Entry requestLifetime = request.getCacheEntry();// 執(zhí)行到這里說明服務(wù)端沒有改動(dòng)結(jié)果,因此直接取緩存
                if(requestLifetime == null) {
                    return new NetworkResponse(304, (byte[])null, responseHeaders, true, SystemClock.elapsedRealtime() - requestStart);
                }

                requestLifetime.responseHeaders.putAll(responseHeaders);
                return new NetworkResponse(304, requestLifetime.data, requestLifetime.responseHeaders, true, SystemClock.elapsedRealtime() - requestStart);
            } catch (SocketTimeoutException var12) {
            }
        }
    }

2.5 HurlStack

很明顯腊敲,我們要跟進(jìn)核心部分mHttpStack.performRequest(request, e)击喂,這里是通過接口的向上轉(zhuǎn)型,我拿HurlStack實(shí)現(xiàn)類來講解兔仰,因?yàn)镠ttpStack已經(jīng)基本用不到了茫负。

    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap map = new HashMap();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);

        //這部分是參數(shù)的拼接,準(zhǔn)備用來請求
        URL parsedUrl1 = new URL(url);
        HttpURLConnection connection = this.openConnection(parsedUrl1, request);
        Iterator responseCode = map.keySet().iterator();

        while(responseCode.hasNext()) {
            String protocolVersion = (String)responseCode.next();
            connection.addRequestProperty(protocolVersion, (String)map.get(protocolVersion));
        }

        setConnectionParametersForRequest(connection, request);
        ProtocolVersion protocolVersion1 = new ProtocolVersion("HTTP", 1, 1);
        int responseCode1 = connection.getResponseCode();
        if(responseCode1 == -1) {
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        } else {
            BasicStatusLine responseStatus = new BasicStatusLine(protocolVersion1, connection.getResponseCode(), connection.getResponseMessage());
            BasicHttpResponse response = new BasicHttpResponse(responseStatus);
            response.setEntity(entityFromConnection(connection));// 這句才是真正的網(wǎng)絡(luò)請求乎赴,返回結(jié)果后賦予response
            Iterator var12 = connection.getHeaderFields().entrySet().iterator();

            while(var12.hasNext()) {
                Entry header = (Entry)var12.next();
                if(header.getKey() != null) {
                    BasicHeader h = new BasicHeader((String)header.getKey(), (String)((List)header.getValue()).get(0));
                    response.addHeader(h);
                }
            }

            return response;
        }
    }

2.6 ExecutorDelivery

在通過HurlStack獲取HttpResponse結(jié)果后忍法,我們返回到BasicNetwork繼續(xù)讀代碼,發(fā)現(xiàn)將HttpResponse轉(zhuǎn)換成NetworkResponse并且返回到NetworkDispatcher榕吼,最后執(zhí)行了mDelivery.postResponse(request, volleyError1)分發(fā)結(jié)果饿序。

    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        //調(diào)用了自定義的Runnable,其實(shí)是將子線程切換到了主線程
        this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, runnable));
    }

    private class ResponseDeliveryRunnable implements Runnable {
        private final Request mRequest;
        private final Response mResponse;
        private final Runnable mRunnable;

        public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
            this.mRequest = request;
            this.mResponse = response;
            this.mRunnable = runnable;
        }

        public void run() {
            if(this.mRequest.isCanceled()) {// 請求取消
                this.mRequest.finish("canceled-at-delivery");
            } else {
                if(this.mResponse.isSuccess()) {
                    this.mRequest.deliverResponse(this.mResponse.result);// 分發(fā)結(jié)果給request羹蚣,request最終分發(fā)給監(jiān)聽器
                } else {
                    this.mRequest.deliverError(this.mResponse.error);
                }

            }
        }
    }

2.7 CacheDispatcher

上面已經(jīng)完成了一個(gè)完整的網(wǎng)絡(luò)請求并且將結(jié)果分發(fā)給監(jiān)聽器原探,供開發(fā)者使用結(jié)果。在之前我們先講解了NetworkDispatcher顽素,并且也看到了mCache.put(request.getCacheKey(), volleyError1.cacheEntry)咽弦,將緩存實(shí)體存入緩存對象中。CacheDispatcher也是一個(gè)線程胁出,循環(huán)來取緩存隊(duì)列里的請求:

    public void run() {
        //省略部分代碼...
        this.mCache.initialize();// 緩存類初始化型型,主要是在SD卡里創(chuàng)建緩存文件夾
        while(true) {
            final Request e = (Request)this.mCacheQueue.take();// 拿出緩存請求
            e.addMarker("cache-queue-take");
            if(e.isCanceled()) {// 請求取消
                e.finish("cache-discard-canceled");
            } else {
                Cache.Entry entry = this.mCache.get(e.getCacheKey());// 根據(jù)key拿到緩存實(shí)體
                if(entry == null) {
                    e.addMarker("cache-miss");
                    this.mNetworkQueue.put(e);// 如果沒有緩存則加入網(wǎng)絡(luò)請求隊(duì)列進(jìn)行網(wǎng)絡(luò)請求
                } else if(entry.isExpired()) {// 過期的也要加入網(wǎng)絡(luò)請求隊(duì)列
                    e.addMarker("cache-hit-expired");
                    e.setCacheEntry(entry);
                    this.mNetworkQueue.put(e);
                } else {
                    e.addMarker("cache-hit");
                    Response response = e.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));// 將NetworkResponse轉(zhuǎn)換成Response
                    e.addMarker("cache-hit-parsed");
                    if(entry.refreshNeeded()) {// 新鮮度判斷,如果需要刷新則在分發(fā)結(jié)果的同時(shí)加入網(wǎng)絡(luò)請求隊(duì)列全蝶,去獲取最新的結(jié)果
                        e.addMarker("cache-hit-refresh-needed");
                        e.setCacheEntry(entry);
                        response.intermediate = true;
                        this.mDelivery.postResponse(e, response, new Runnable() {
                            public void run() {
                                try {
                                    CacheDispatcher.this.mNetworkQueue.put(e);
                                } catch (InterruptedException var2) {
                                    ;
                                }

                            }
                        });
                    } else {
                        this.mDelivery.postResponse(e, response);// 分發(fā)結(jié)果(和NetworkDispatcher一樣)
                    }
                }
            }
        }
    }

三闹蒜、總結(jié)

隨著一點(diǎn)點(diǎn)的跟進(jìn)代碼,我們理清了Volley的工作流程:
1.初始化網(wǎng)絡(luò)請求類(BasicNetwork)抑淫、緩存類(DiskBasedCache)等绷落,創(chuàng)建請求隊(duì)列(RequestQueue)并啟動(dòng)緩存請求線程(CacheDispatcher)和網(wǎng)絡(luò)請求線程(NetworkDispatcher )。
2.CacheDispatcher和NetworkDispatcher不斷輪詢?nèi)〕稣埱笫嘉绻芯彺鎰t進(jìn)行過期時(shí)間砌烁、新鮮度等判斷,來決定是從Cache中取緩存還是再去進(jìn)行網(wǎng)絡(luò)請求催式。網(wǎng)絡(luò)請求通過BasicNetwork調(diào)用HttpStack來返回請求結(jié)果后會(huì)進(jìn)行緩存(除非你自己設(shè)置不進(jìn)行緩存)函喉。
3.最后通過ExecutorDelivery實(shí)現(xiàn)類分發(fā)給我們創(chuàng)建的Request中,而根據(jù)我們使用的Request(StringRequest蓄氧、JsonRequest、ImageRequest等)返回對應(yīng)的數(shù)據(jù)結(jié)構(gòu)槐脏。
以上就是Volley的主要工作原理喉童,我覺得先看懂主要原理是重要的一步,理解原理后再去看代碼細(xì)節(jié),比如Volley中的PoolingByteArrayOutputStream(繼承ByteArrayOutputStream并給二進(jìn)制輸出流增加緩存區(qū))堂氯、HttpHeaderParser(用來解析返回結(jié)果的頭部)等蔑担。

參考:

http://a.codekk.com/detail/Android/grumoon/Volley%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咽白,隨后出現(xiàn)的幾起案子啤握,更是在濱河造成了極大的恐慌,老刑警劉巖晶框,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件排抬,死亡現(xiàn)場離奇詭異,居然都是意外死亡授段,警方通過查閱死者的電腦和手機(jī)蹲蒲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侵贵,“玉大人届搁,你說我怎么就攤上這事∏嫌” “怎么了卡睦?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長漱抓。 經(jīng)常有香客問我表锻,道長,這世上最難降的妖魔是什么辽旋? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任浩嫌,我火速辦了婚禮,結(jié)果婚禮上补胚,老公的妹妹穿的比我還像新娘码耐。我一直安慰自己,他們只是感情好溶其,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布骚腥。 她就那樣靜靜地躺著,像睡著了一般瓶逃。 火紅的嫁衣襯著肌膚如雪束铭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天厢绝,我揣著相機(jī)與錄音契沫,去河邊找鬼。 笑死昔汉,一個(gè)胖子當(dāng)著我的面吹牛懈万,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼会通,長吁一口氣:“原來是場噩夢啊……” “哼口予!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起涕侈,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沪停,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后裳涛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體木张,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年调违,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窟哺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡技肩,死狀恐怖且轨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情虚婿,我是刑警寧澤旋奢,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站然痊,受9級特大地震影響至朗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剧浸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一锹引、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唆香,春花似錦嫌变、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冯吓,卻和暖如春倘待,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背组贺。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工凸舵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人失尖。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓啊奄,卻偏偏與公主長得像贿条,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子增热,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容