Volley源碼解析

前言

這段時間入職新公司,發(fā)現(xiàn)網(wǎng)絡(luò)請求庫使用的是Volley捺信,由于對Volley還不是很熟悉豪嚎,于是有了今天這篇文章。

Volley的基本使用

        //Volley的回調(diào)是在主線程的
        Log.e("TAG", "volley: -----------"+ Thread.currentThread().getName());
        //1.創(chuàng)建請求隊列
        RequestQueue requestQueue = Volley.newRequestQueue(this);
        String url = "https://api.apiopen.top/musicBroadcasting";
        //2.創(chuàng)建請求
        StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Log.e("TAG", "onResponse: ---" + Thread.currentThread().getName());
                Log.e("TAG", "onResponse: ---" + response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e("TAG", "onErrorResponse: ---" + Thread.currentThread().getName());
            }
        });
        //3.將創(chuàng)建的請求添加到隊列中
        requestQueue.add(request);
  • 注意: Volley的回調(diào)是在主線程的所以可以直接在回調(diào)中進(jìn)行UI操作

分析

創(chuàng)建請求隊列 Volley.newRequestQueue(this);做了些什么

    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, (BaseHttpStack) null);
    }
    //調(diào)用重載方法 BaseHttpStack 為null

    public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
        BasicNetwork network;
        if (stack == null) {
            //安卓2.3及以上使用HttpURLConnection
            if (Build.VERSION.SDK_INT >= 9) {
                network = new BasicNetwork(new HurlStack());
            } else {
                //下面這段話的意思是安卓2.3以前HttpURLConnection不可靠泻轰,所以使用的Apache的HttpClient,后面如果不再兼容2.3以下會考慮把Apache的Http移除
                // 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) {
                }

                //采用HttpClient
                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) {
        //創(chuàng)建緩存路徑
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        // 創(chuàng)建請求隊列
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();
        return queue;
    }

    public void start() {
        //一個Queue里面有一個緩存線程和默認(rèn)4個網(wǎng)絡(luò)請求線程且轨。也就是說內(nèi)部其實會啟動5個線程
        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();
        }
    }

Request

  • Request就是封裝了一些請求相關(guān)的東西浮声,這里暫時略過

requestQueue.add(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.
        //這里的意思就是把請求和隊列關(guān)聯(lián)起來,這個請求屬于這個隊列
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {//添加到請求集合里面 set集合
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added. 這一步是可以讓請求按照添加順序去處理  直接維護(hù)一個隊列不是更好嗎旋奢?泳挥??
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");
        // If the request is uncacheable, skip the cache queue and go straight to the network.
        //判斷是否需要緩存responses 如果不需要就跳過緩存隊列  直接到網(wǎng)絡(luò)請求隊列  值得注意的是Volley默認(rèn)是做緩存的
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
        mCacheQueue.add(request);
        return request;
    }
  • 接下來就是看這幾個線程NetworkDispatcher和CacheDispatcher做了些什么
    直接看他們的run方法

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();
        //一個死循環(huán)處理請求
        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");
            }
        }
    }
    //從隊列里面拿出請求然后處理
    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);
    }

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

        // If the request has been canceled, don't bother dispatching it.
        if (request.isCanceled()) {
            request.finish("cache-discard-canceled");
            return;
        }

        // Attempt to retrieve this item from cache.
        Cache.Entry entry = mCache.get(request.getCacheKey());
        if (entry == null) {
            request.addMarker("cache-miss");
            // Cache miss; send off to the network dispatcher.
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

        // If it is completely expired, just send it to the network.
        if (entry.isExpired()) {
            request.addMarker("cache-hit-expired");
            request.setCacheEntry(entry);
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

        // We have a cache hit; parse its data for delivery back to the request.
        request.addMarker("cache-hit");
        Response<?> response =
                request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
        request.addMarker("cache-hit-parsed");

        if (!entry.refreshNeeded()) {
            // Completely unexpired cache hit. Just deliver the response.
            mDelivery.postResponse(request, response);
        } else {
            // Soft-expired cache hit. We can deliver the cached response,
            // but we need to also send the request to the network for
            // refreshing.
            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) {
                                    // Restore the interrupted status
                                    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);
            }
        }
    }


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

            // If the request was cancelled already, do not perform the
            // network request.
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                request.notifyListenerResponseNotUsable();
                return;
            }

            addTrafficStatsTag(request);
            //執(zhí)行網(wǎng)絡(luò)請求的地方   通過HttpUrlConnection 或者HttpClient來請求數(shù)據(jù)
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");

            // If the server returned 304 AND we delivered a response already,
            // we're done -- don't deliver a second identical response.
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                request.notifyListenerResponseNotUsable();
                return;
            }

            // Parse the response here on the worker thread.
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");

            // Write to cache if applicable.
            // TODO: Only update cache metadata instead of entire record for 304s.
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

            // Post the response back.
            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();
        }
    }
    

緩存處理

  • Volley判斷是否需要刷新緩存是使用服務(wù)端設(shè)置的至朗,會考慮服務(wù)端返回header里的Cache-Control的Expires屉符。但是有時候接口并不返回這些東西,這種情況下锹引,volley設(shè)置的緩存ttl就是0,也就是相當(dāng)于沒有緩存矗钟,每次都會從網(wǎng)絡(luò)請求
  • 如果需要緩存,應(yīng)該怎么設(shè)置嫌变?
  • 重寫請求頭信息原理就是增加過期時間
public class CacheControlHttpHeaderParser extends HttpHeaderParser {

    /**
     * @param response
     * @param cacheTime 設(shè)置緩存時間
     * @return
     */
    public static Cache.Entry parseCacheHeaders(NetworkResponse response, long cacheTime) {
        long now = System.currentTimeMillis();
        Map<String, String> headers = response.headers;
        long serverDate = 0;
        long lastModified = 0;
        String serverEtag = null;
        String headerValue;

        headerValue = headers.get("Date");
        if (headerValue != null) {
            serverDate = parseDateAsEpoch(headerValue);
        }
        headerValue = headers.get("Last-Modified");
        if (headerValue != null) {
            lastModified = parseDateAsEpoch(headerValue);
        }
        serverEtag = headers.get("ETag");
        Cache.Entry entry = new Cache.Entry();
        entry.data = response.data;
        entry.etag = serverEtag;
        entry.softTtl = now + cacheTime;
        entry.ttl = now + cacheTime;
        entry.serverDate = serverDate;
        entry.lastModified = lastModified;
        entry.responseHeaders = headers;
        entry.allResponseHeaders = response.allHeaders;

        return entry;
    }
}
  • 在Request里重寫parseNetworkResponse
public class StringCacheRequest extends StringRequest {
    private static final long CACHE_TIME = 1000 * 60 * 5;//5分鐘

    public StringCacheRequest(int method, String url, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener) {
        super(method, url, listener, errorListener);
    }

    public StringCacheRequest(String url, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener) {
        super(url, listener, errorListener);
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, CacheControlHttpHeaderParser.parseCacheHeaders(response, CACHE_TIME));
    }
}

總結(jié)

Volley的源碼對于開發(fā)者還是比較友好的吨艇,沒什么地方繞彎子√谏叮看起來也是行云流水东涡,酣暢淋漓。對于安卓開發(fā)者從長遠(yuǎn)看還是推薦使用OkHttp倘待,OkHttp效率更高疮跑,設(shè)計更為合理。并且支持Http2凸舵、SPDY祖娘。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市啊奄,隨后出現(xiàn)的幾起案子渐苏,更是在濱河造成了極大的恐慌,老刑警劉巖增热,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件整以,死亡現(xiàn)場離奇詭異,居然都是意外死亡峻仇,警方通過查閱死者的電腦和手機(jī)公黑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凡蚜,你說我怎么就攤上這事人断。” “怎么了朝蜘?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵恶迈,是天一觀的道長。 經(jīng)常有香客問我谱醇,道長暇仲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任副渴,我火速辦了婚禮奈附,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘煮剧。我一直安慰自己斥滤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布勉盅。 她就那樣靜靜地躺著佑颇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪草娜。 梳的紋絲不亂的頭發(fā)上挑胸,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音驱还,去河邊找鬼嗜暴。 笑死凸克,一個胖子當(dāng)著我的面吹牛议蟆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萎战,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼咐容,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蚂维?” 一聲冷哼從身側(cè)響起戳粒,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虫啥,沒想到半個月后蔚约,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡涂籽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年苹祟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡树枫,死狀恐怖直焙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砂轻,我是刑警寧澤奔誓,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站搔涝,受9級特大地震影響厨喂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庄呈,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一杯聚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抒痒,春花似錦幌绍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至彩届,卻和暖如春伪冰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背樟蠕。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工贮聂, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寨辩。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓吓懈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親靡狞。 傳聞我的和親對象是個殘疾皇子耻警,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

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

  • 注:本文轉(zhuǎn)自http://codekk.com/open-source-project-analysis/deta...
    Ten_Minutes閱讀 1,296評論 1 16
  • 抽時間看了下Volley的源碼,感覺這個框架還是很不錯的甸怕,這里對其源碼進(jìn)行分析甘穿。 GitHub鏈接 主要從以下幾個...
    木有粗面_9602閱讀 234評論 0 0
  • Volley已是老牌的網(wǎng)絡(luò)請求庫了(雖然也就3歲不到),雖然是Google官方的庫梢杭,但是目前OkHttp才正是大行...
    LeonXtp閱讀 416評論 0 1
  • 在Volley 源碼解析及對 Volley 的擴(kuò)展系列的第一篇文章中温兼,介紹了一種通過繼承 StringReques...
    lijiankun24閱讀 609評論 0 2
  • Volley的優(yōu)缺點 優(yōu)點 自動的調(diào)度網(wǎng)絡(luò)請求 多并發(fā)的網(wǎng)絡(luò)請求 可以緩存http請求 支持請求的優(yōu)先級 支持取消...
    礪雪凝霜閱讀 2,955評論 0 5