Android 網(wǎng)絡(luò)框架 Volley 源碼解析

Volley 是 Google 官方推出的一套 Android 網(wǎng)絡(luò)請(qǐng)求庫(kù)蜒蕾,特別適用于通信頻繁薇芝、數(shù)據(jù)量較小的網(wǎng)絡(luò)請(qǐng)求茫死。Volley 能夠根據(jù)當(dāng)前手機(jī)版本選擇 HttpClient (2.3 以下) 或者 HttpUrlConnection路翻。

除了 Volley割捅,Android 常用的網(wǎng)絡(luò)加載庫(kù)還有 OkHttp,Retrofit 等帚桩,關(guān)于這幾個(gè)的區(qū)別請(qǐng)移步 stormzhong 的ANDROID開(kāi)源項(xiàng)目推薦之「網(wǎng)絡(luò)請(qǐng)求哪家強(qiáng)]

Volley 框架擴(kuò)展性很強(qiáng),其源碼值得我們好好學(xué)習(xí)嘹黔。

先了解一些 Http 協(xié)議

Http 協(xié)議規(guī)范了客戶端和服務(wù)端數(shù)據(jù)請(qǐng)求的一套規(guī)則账嚎。Http 協(xié)議規(guī)范了 請(qǐng)求(Request)和響應(yīng)(Response)。

請(qǐng)求包含請(qǐng)求行儡蔓,請(qǐng)求頭(Header)郭蕉,body(可選)。如下圖所示(圖片來(lái)源于網(wǎng)絡(luò)):


Http Request

請(qǐng)求行必須指定請(qǐng)求方式喂江,常見(jiàn)的有 GET召锈,POST等。

響應(yīng)和請(qǐng)求類似获询。也包含三部分涨岁,狀態(tài)行(含響應(yīng)碼)拐袜,響應(yīng)頭(Header),body(可選)梢薪。如下圖所示(圖片來(lái)源于網(wǎng)絡(luò)):


Http Response

關(guān)于 Http 協(xié)議大致了解這些即可蹬铺。

Volley 對(duì) Http Request 和 Response 的封裝。

請(qǐng)求封裝

Http 請(qǐng)求包含三部分秉撇,請(qǐng)求行甜攀,請(qǐng)求頭,body琐馆。Volley 將其封裝成 Request规阀,Request 有幾個(gè)實(shí)現(xiàn)類,如 StringRequest瘦麸,JsonObjectRequest 等谁撼。Request 部分源碼如下:

public abstract class Request<T> implements Comparable<Request<T>> {

    public interface Method {
        int DEPRECATED_GET_OR_POST = -1;
        int GET = 0;
        int POST = 1;
        int PUT = 2;
        int DELETE = 3;
        int HEAD = 4;
        int OPTIONS = 5;
        int TRACE = 6;
        int PATCH = 7;
    }
    // 對(duì)應(yīng)請(qǐng)求頭中的 Method
    private final int mMethod;

    // 對(duì)應(yīng)請(qǐng)求頭中的 URL
    private final String mUrl;
    // 對(duì)應(yīng)請(qǐng)求頭
    public Map<String, String> getHeaders() throws AuthFailureError {
        return Collections.emptyMap();
    }
    // 對(duì)應(yīng)請(qǐng)求實(shí)體
    public byte[] getBody() throws AuthFailureError {
        Map<String, String> params = getParams();
        if (params != null && params.size() > 0) {
            return encodeParameters(params, getParamsEncoding());
        }
        return null;
    }
}

響應(yīng)封裝

Http 響應(yīng)包含三部分,狀態(tài)行瞎暑,響應(yīng)頭彤敛,body。
Volley 對(duì) Http 響應(yīng)用 NetworkResponse 封裝了赌,NetworkResponse 部分源碼如下:

public class NetworkResponse {
    /** 狀態(tài)行中的響應(yīng)碼 */
    public final int statusCode;

    /** body */
    public final byte[] data;

    /** 響應(yīng)頭 */
    public final Map<String, String> headers;

    /** True if the server returned a 304 (Not Modified). */
    public final boolean notModified;

    /** Network roundtrip time in milliseconds. */
    public final long networkTimeMs;
}

NetworkResponse 直觀的表示 Http 響應(yīng)返回的數(shù)據(jù)墨榄。但是我們的應(yīng)用程序不能直接使用其中的 body(字節(jié)數(shù)組),需要解析成某個(gè) Bean 對(duì)象勿她,這樣程序就可以直接使用 Bean 對(duì)象袄秩。因此還需要對(duì)響應(yīng)做進(jìn)一步的封裝。顯然這個(gè) Bean 的數(shù)據(jù)類型不可知逢并,可以使用 java 中的泛型之剧。

Volley 對(duì)響應(yīng)進(jìn)一步封裝成 Response,Response 部分源碼碼如下:


public class Response<T> {
  
    /** 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;
    
}

由 NetworkResponse 到 Response 需要一個(gè)方法將 body 解析成某個(gè)對(duì)象砍聊。Volley 將這個(gè)方法定義在 Request 中背稼,而且是一個(gè)抽象方法〔r颍看下 StringRequest 對(duì)這個(gè)方法的實(shí)現(xiàn):

    @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, HttpHeaderParser.parseCacheHeaders(response));
    }

其實(shí)就是對(duì)返回的字節(jié)數(shù)組(body)解析成某個(gè)對(duì)象蟹肘,StringRequest 則解析成 String,JsonObjectRequest 則解析成 JsonObject 等俯树。我們可以定義一個(gè)自己的 Request帘腹,在 parseNetworkResponse 方法中將字節(jié)數(shù)組轉(zhuǎn)成 String,再用 Gson 解析成我們的對(duì)象许饿。

發(fā)起一個(gè)請(qǐng)求

Volley 的簡(jiǎn)單使用如下:

RequestQueue mQueue = Volley.newRequestQueue(context);

StringRequest stringRequest = new StringRequest("http://www.baidu.com",
        new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Log.d("TAG", response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e("TAG", error.getMessage(), error);
            }
        });
mQueue.add(stringRequest);

Volley.newRequestQueue(context) 會(huì)創(chuàng)建一個(gè) RequestQueue阳欲,并調(diào)用RequestQueue #start()

RequestQueue 表示請(qǐng)求隊(duì)列,是 Volley 框架的核心類球化。RequestQueue 包含一個(gè)網(wǎng)絡(luò)隊(duì)列 mNetworkQueue 和一個(gè)緩存隊(duì)列 mCacheQueue 秽晚,作為其成員變量。

RequestQueue 部分源碼如下:

public class RequestQueue {
    /** 緩存隊(duì)列 */
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =
        new PriorityBlockingQueue<Request<?>>();

    /** 網(wǎng)絡(luò)隊(duì)列 */
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<Request<?>>();  
        
    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 方法中赊窥,開(kāi)啟一個(gè)緩存調(diào)度線程 CacheDispatcher爆惧,用來(lái)處理緩存。默認(rèn)開(kāi)啟四個(gè)網(wǎng)絡(luò)調(diào)度線程 NetworkDispatcher锨能,用來(lái)處理網(wǎng)絡(luò)請(qǐng)求扯再。CacheDispatcher 不斷的從 mCacheQueue 中取走 Request 并進(jìn)行分發(fā)處理。NetworkDispatcher 不斷的從 mNetworkQueue 取走 Request 并進(jìn)行分發(fā)處理址遇。如果隊(duì)列為空熄阻,則阻塞等等,這些線程都是常駐內(nèi)存隨時(shí)待命的倔约。顯然一個(gè)程序中最好只能有一個(gè) RequestQueue秃殉,如果采用多個(gè),應(yīng)該在適當(dāng)?shù)臅r(shí)候調(diào)用 RequestQueue#stop()銷毀線程釋放內(nèi)存浸剩。

當(dāng)我們向 RequestQueue 添加一個(gè) Request 時(shí)钾军,如果 Request 可緩存則添加到 mCacheQueue ,否則添加到 mNetworkQueue 绢要。CacheDispatcher 拿到這個(gè) Request吏恭,如果緩存存在并且還沒(méi)過(guò)期,則解析數(shù)據(jù)并提及給主線程重罪,否則添加到 mNetworkQueue 交給 NetworkDispatcher 處理樱哼。流程如下:


這里寫(xiě)圖片描述

緩存如何處理?

app 網(wǎng)絡(luò)數(shù)據(jù)緩存

Cache 的實(shí)現(xiàn)類為 DiskBasedCache剿配。DiskBasedCache 采用磁盤(pán)緩存和內(nèi)存緩存搅幅,但兩者緩存的數(shù)據(jù)不一樣。內(nèi)存緩存只緩存 CacheHeader呼胚,而磁盤(pán)緩存的是 Entry茄唐,只不過(guò)是將 Entry 中的數(shù)據(jù)按一定規(guī)則寫(xiě)到文件中,讀取緩存時(shí)再按照同樣的規(guī)則讀取到 Entry 中蝇更。另外沪编,Entry 比 CacheHeader 多了一個(gè)字節(jié)數(shù)組,顯然這是比較占內(nèi)存的簿寂,因此內(nèi)存緩存并沒(méi)有緩存 Entry。

當(dāng)緩存滿了之后如何處理呢宿亡?

DiskBasedCache 中有個(gè)方法是pruneIfNeeded(int neededSpace),每次執(zhí)行 put 的時(shí)都會(huì)先調(diào)用該方法常遂。這個(gè)方法就會(huì)刪除較早的緩存。內(nèi)存緩存保存在 mEntries 中挽荠。我們看下這個(gè)成員變量:

    /** Map of the Key, CacheHeader pairs */
    private final Map<String, CacheHeader> mEntries =
            new LinkedHashMap<String, CacheHeader>(16, .75f, true);

LinkedHashMap 有個(gè)構(gòu)造函數(shù)為 LinkedHashMap( int initialCapacity, float loadFactor, boolean accessOrder)最后一個(gè)參數(shù) accessOrder 就表示是否按照訪問(wèn)順序排列克胳。當(dāng) accessOrder 為 true平绩,最后執(zhí)行 get 或者 put 的元素會(huì)在 LinkedHashMap 的尾部。

這樣 pruneIfNeeded 方法就很容易找到較早的緩存并將其刪除漠另。

服務(wù)端緩存

當(dāng)客戶端發(fā)起一個(gè) Http 請(qǐng)求捏雌,如果服務(wù)端返回 304,表示請(qǐng)求的資源緩存仍能有效笆搓。這樣就能減少數(shù)據(jù)傳輸性湿。當(dāng)然,這種方式需要客戶端攜帶額外的頭信息满败,Volley 已經(jīng)幫我們做了這部分肤频。直接看相應(yīng)的源碼:

public class BasicNetwork implements Network {
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        // 省略...
        Map<String, String> headers = new HashMap<String, String>();
        addCacheHeaders(headers, request.getCacheEntry());
        // before request
        httpResponse = mHttpStack.performRequest(request, headers);
        StatusLine statusLine = httpResponse.getStatusLine();
        int statusCode = statusLine.getStatusCode();
        responseHeaders = convertHeaders(httpResponse.getAllHeaders());
        // Http 304
        if (statusCode == HttpStatus.SC_NOT_MODIFIED) {

        }
    }
    private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
        // If there's no cache entry, we're done.
        if (entry == null) {
            return;
        }

        if (entry.etag != null) {
            headers.put("If-None-Match", entry.etag);
        }

        if (entry.lastModified > 0) {
            Date refTime = new Date(entry.lastModified);
            headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
        }
    }
}

從上可以看到 Volley 在請(qǐng)求頭添加了一個(gè) etag 和 lastModified,這些數(shù)據(jù)來(lái)自上次請(qǐng)求的響應(yīng)頭中算墨,Volley對(duì)其做了緩存宵荒,并且在下一次請(qǐng)求時(shí)添加到請(qǐng)求頭中。這樣服務(wù)端就能比較客戶端發(fā)送的 etag 和自己的 etag净嘀,如果相等报咳,說(shuō)明請(qǐng)求的資源未發(fā)生變化,服務(wù)端返回304挖藏∈钊校客戶端則對(duì)響應(yīng)碼做判斷,如果為 304熬苍,說(shuō)明本地緩存有效稍走。

主線程回調(diào)

CacheDispatcher 和 NetworkDispatcher 都是運(yùn)行在非主線程當(dāng)中的,而我們的 UI 必須在主線程中更新柴底。Volley 采用的是 Handler 來(lái)通知主線程更新 UI婿脸。

Request 中有一個(gè) deliverResponse 和 deliverError,一個(gè)是成功回調(diào)柄驻,另一個(gè)是失敗回調(diào)狐树。那么這兩個(gè)方法是什么時(shí)候被執(zhí)行的呢?

看下 NetworkDispatcher 部分源碼(省略部分代碼)

public class NetworkDispatcher extends Thread {
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            try {
                request = mQueue.take();
            } catch (InterruptedException e) {
            }

            try{
                // 發(fā)起網(wǎng)絡(luò)請(qǐng)求
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                // 解析
                Response<?> response = request.parseNetworkResponse(networkResponse);
                // 寫(xiě)緩存
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                }
                // 分發(fā)結(jié)果鸿脓,通知主線程
                mDelivery.postResponse(request, response);

            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                // 分發(fā)失敗
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                // 分發(fā)失敗
                mDelivery.postError(request, volleyError);
            }
        }
    }
}

和主線程相關(guān)的就是 mDelivery.postResponse(request, response); 和 mDelivery.postError(request, volleyError)抑钟。

我們看下 NetworkDispatcher 中 mDelivery 是如何創(chuàng)建的∫翱蓿看下 NetworkDispatcher 源碼發(fā)現(xiàn)是在構(gòu)造函數(shù)在塔,于是去 RequestQueue 找 NetworkDispatcher 對(duì)象的創(chuàng)建過(guò)程。在 start 方法中會(huì)創(chuàng)建 NetworkDispatcher 拨黔,并傳入一個(gè) mDelivery 對(duì)象蛔溃,而 mDelivery 在 RequestQueue 的構(gòu)造函數(shù)中已經(jīng)完成了初始化,看下相關(guān)源碼:

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                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;
    }

由此可見(jiàn)在 NetworkDispatcher 中 mDelivery 的實(shí)際類型是 ExecutorDelivery。ExecutorDelivery 的構(gòu)造函數(shù)接收一個(gè) Handler 用來(lái)往主線程發(fā)消息贺待。

看下 ExecutorDelivery 部分源碼:

public class ExecutorDelivery implements ResponseDelivery {
    /** Used for posting responses, typically to the main thread. */
    private final Executor mResponsePoster;

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

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

postResponse 和 postError 最終都會(huì)提交 一個(gè) Runnable 給 mResponsePoster 執(zhí)行徽曲,而 mResponsePoster 則將這個(gè) Runnable 提交給 Handler 去執(zhí)行。Handler 接收到 Runnable 之后最終會(huì)執(zhí)行 mRequest.deliverResponse(mResponse.result) 或者 mRequest.deliverError(mResponse.error)完成主線程的回調(diào)麸塞。

小結(jié)

關(guān)于 Volley 的源碼大致先解讀這些秃臣,重點(diǎn)在于理順整個(gè)邏輯,其他的像 HttpClient哪工,HttpUrlConnection 的網(wǎng)絡(luò)操作建議直接閱讀源碼奥此。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市正勒,隨后出現(xiàn)的幾起案子得院,更是在濱河造成了極大的恐慌,老刑警劉巖章贞,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祥绞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鸭限,警方通過(guò)查閱死者的電腦和手機(jī)蜕径,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)败京,“玉大人兜喻,你說(shuō)我怎么就攤上這事∩穆螅” “怎么了朴皆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)泛粹。 經(jīng)常有香客問(wèn)我遂铡,道長(zhǎng),這世上最難降的妖魔是什么扒接? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任们衙,我火速辦了婚禮,結(jié)果婚禮上蒙挑,老公的妹妹穿的比我還像新娘宗侦。我一直安慰自己,他們只是感情好忆蚀,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著梦皮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪桃焕。 梳的紋絲不亂的頭發(fā)上剑肯,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音让网,去河邊找鬼师痕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛因篇,可吹牛的內(nèi)容都是我干的笔横。 我是一名探鬼主播竞滓,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼商佑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼厢塘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起抓半,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤迄薄,失蹤者是張志新(化名)和其女友劉穎讥蔽,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體冶伞,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡响禽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年荚醒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了界阁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胖喳。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丽焊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情技健,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布啊送,位于F島的核電站欣孤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏披泪。R本人自食惡果不足惜搬瑰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望艾少。 院中可真熱鬧翼悴,春花似錦、人聲如沸谍椅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杖们。三九已至,卻和暖如春摘完,著一層夾襖步出監(jiān)牢的瞬間孝治,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留力图,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓瓤介,卻偏偏與公主長(zhǎng)得像赘那,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祠斧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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