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è)抽象方法??聪?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??蛻舳藙t對(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容