源碼學(xué)習(xí)|Volley網(wǎng)絡(luò)庫源碼分析

Volley是Google I/O 2013上發(fā)布的一個網(wǎng)絡(luò)通信庫,本文將基于Android N Frameworks層中的Volley源碼進(jìn)行分析。

Volley框架介紹:

在計(jì)算機(jī)網(wǎng)絡(luò)發(fā)展中,誕生了兩種經(jīng)典的計(jì)算機(jī)網(wǎng)絡(luò)參考模型:OSI參考模型與TCP/IP參考模型,其中的分層如下所示:

OSI七層模型 TCP/IP四層模型 傳輸?shù)臄?shù)據(jù)
應(yīng)用層 應(yīng)用層 數(shù)據(jù)
表示層 應(yīng)用層 數(shù)據(jù)
會話層 應(yīng)用層 數(shù)據(jù)
傳輸層 傳輸層
網(wǎng)絡(luò)層 網(wǎng)際互聯(lián)層
數(shù)據(jù)鏈路層 網(wǎng)絡(luò)接入層
物理層 網(wǎng)絡(luò)接入層 比特流

而目前Android開發(fā)中常提及的各種網(wǎng)絡(luò)框架面向的網(wǎng)絡(luò)層也是不同的,針對TCP/IP四層模型中,現(xiàn)在使用較多的OKHttp,HttpClient, URLConnection都是面向傳輸層的網(wǎng)絡(luò)框架,而Retrofit, AsyncHttp, Volley則是依托于這些面向傳輸層的網(wǎng)絡(luò)框架。其中Retrofit中默認(rèn)采用OKHttp作為其網(wǎng)絡(luò)傳輸層,AsyncHttp則默認(rèn)采用HttpClient,而Volley中,對于API Level大于8的采用URLConnection作為網(wǎng)絡(luò)傳輸層,而小于等于8的采用HttpClient作為網(wǎng)絡(luò)傳輸層,同時它支持定義OKHttp作為其網(wǎng)絡(luò)傳輸層。對于一些說Volley是基于OKHttp進(jìn)行封裝的網(wǎng)絡(luò)庫,這種說法其實(shí)并不準(zhǔn)確。

Volley簡單使用

Volley的使用大致分為三步:請求初始化、構(gòu)建請求參數(shù)、設(shè)置請求回調(diào)、執(zhí)行請求。如下所示是簡單的Volley請求寫法。

RequestQueue mQueue = Volley.newRequestQueue(context);  
StringRequest stringRequest = new StringRequest(url,  
                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源碼分析

Android 7.0 源碼中關(guān)于Volley的庫位于frameworks/volley包下。現(xiàn)我們將按Volley的使用步驟進(jìn)行源碼的分析:

Volly RequestQueue的初始化

Volley調(diào)用的newRequestQueue有兩類構(gòu)造方法:newRequestQueue(Context context)newRequestQueue(Context context, HttpStack stack),如果不選擇傳入HttpStack,則Volley將以傳入null的形式調(diào)用后一方法。其具體實(shí)現(xiàn)如下所示:

    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
    String userAgent = "volley/0";
    try {
        String packageName = context.getPackageName();
        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
        userAgent = packageName + "/" + info.versionCode;
    } catch (NameNotFoundException e) {
    }
    if (stack == null) {
        if (Build.VERSION.SDK_INT >= 9) {
            stack = new HurlStack();
        } else {
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }
    Network network = new BasicNetwork(stack);
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();

我們可以看到,對于stack的處理:如果默認(rèn)傳入HttpStack的實(shí)例stack為空,則會根據(jù)SDK API版本選擇不同的實(shí)現(xiàn)類.其中HurlStack與HttpClientStack都是HttpStack的實(shí)現(xiàn)類,從名稱上以可以看的出來,HurlStack是封裝的HttpURLConnection,而HttpClientStack是封裝的HttpClient。其具體的封裝與請求使用我會在其他文章中進(jìn)行詳細(xì)分析。此處不再詳述。

BasicNetWork是Network接口的實(shí)現(xiàn)類,其中接口的實(shí)現(xiàn)方法performRequest(Request<?> request)中會調(diào)用請求Request去真正執(zhí)行網(wǎng)絡(luò)請求。此處的start()方法只是調(diào)用RequestQueue的start()方法啟動CacheDispatcher與NetRequestDispatcher兩個請求轉(zhuǎn)發(fā)線程,具體請參考后文的RequestQueue的start方法分析。

Volley RequestQueue的構(gòu)造函數(shù)

在RequestQueue中start()方法啟動前,會調(diào)用RequestQueue構(gòu)造方法進(jìn)行初始化,將NetWork實(shí)現(xiàn)類和默認(rèn)的緩存?zhèn)魅隦equestQueue中,完成RequestQueue的初始化。其中最終調(diào)用的初始化方法如下所示:

    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

在構(gòu)造方法中,RequestQueue中對傳入各部分的參數(shù)注釋如下:

  • cache A Cache to use for persisting responses to disk (Cache 是一個接口定義,默認(rèn)使用DiskCache來持久化響應(yīng)結(jié)果)
  • network A Network interface for performing HTTP requests (Network 是一個接口定義,默認(rèn)使用BasicNetwork來執(zhí)行HTTP請求)
  • threadPoolSize Number of network dispatcher threads to create (使用NetworkDispatcher數(shù)組定義執(zhí)行器容器,默認(rèn)容器最大容量為4)
  • delivery A ResponseDelivery interface for posting responses and errors (請求結(jié)果轉(zhuǎn)發(fā)接口,默認(rèn)使用ExecutorDelivery轉(zhuǎn)發(fā)結(jié)果)
    在構(gòu)造函數(shù)之前的注釋說明的更清楚一點(diǎn):Creates the worker pool. Processing will not begin until {@link #start()} is called.,大意為創(chuàng)建工作池,但是在調(diào)用start方法之前,整個請求并不會真正開始工作。那么接下來我們便來分析RequestQueue的啟動方法start()。
Volley RequestQueue的啟動

RequestQueue中的start()方法中,會真正的開始執(zhí)行一個請求隊(duì)列,而在這之中,RequestQueue又會引入二個新的類型,分別為:CacheDispatcher與NetworkDispatcher,具體代碼如下所示:

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

在啟動任務(wù)之前會調(diào)用stop()方法確保當(dāng)前沒有正在運(yùn)行任務(wù)這個比較好理解,而接下來創(chuàng)建的CacheDispatcher從構(gòu)造方法傳參看,其中又多了一個未曾在前面出現(xiàn)過的mCacheQueue,NetWorkDispatcher也多了一個未曾在前面出現(xiàn)過的mNetworkQueue。這兩個集合類是在類變量聲明時便進(jìn)行初始化了:

    private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();

CacheDispatcher繼承自Thread,從名稱就可以看出,主要作用是管理緩存派發(fā)請求的。而NetworkDispatcher同樣也繼承自Thread,是處理緩存未派發(fā)的請求的。當(dāng)調(diào)用其start()方法后,線程便會啟動。我會在其他文章中對它的緩存派發(fā)和請求管理進(jìn)行詳細(xì)分析。此處不再詳述

Volley 構(gòu)建Request請求與注冊監(jiān)聽

在Volley對RequestQueue初始化完畢后,我們需要構(gòu)建請求,來完成我們的網(wǎng)絡(luò)請求.在簡單使用的代碼中,構(gòu)建請求使用的是StringRequest,它是抽象類Request的子類。Request的子類請求還有ImageRequest、JsonRequest。而JsonRequest是JsonArrayRequest與JsonObjectRequest的父類。對于Request類中,我們注意到,對于Request這個頂級父類,其監(jiān)聽只注冊了一個ErrorListener,而結(jié)果監(jiān)聽則交給其實(shí)現(xiàn)類去注冊,如StringRequest的結(jié)果監(jiān)聽為Listener<String>、ImageRequest的結(jié)果監(jiān)聽為Listener<Bitmap>、JsonArrayRequest注冊的結(jié)果監(jiān)聽為Listener<JSONArray>、JsonObjectRequest注冊的結(jié)果監(jiān)聽為Listener<JSONObject>。而JsonRequest的結(jié)果監(jiān)聽卻支持范型傳入Listener<T>,從這點(diǎn)來說,筆者有些奇怪,既然支持范型定義,為何不直接在頂級父類定義封裝,然后在子類中傳入?yún)?shù)進(jìn)行初始化使用呢? 從軟件分層的角度來看,好像這樣也更合理。希望對這個問題有想法的同學(xué)能提供些思路給我。

而對于不同的Request子類實(shí)現(xiàn)類,筆者會在以后對幾種實(shí)現(xiàn)類進(jìn)行詳細(xì)分析。大致概括說來,Volley會對請求結(jié)果進(jìn)行解析封裝,以對應(yīng)的類型包裝回調(diào)回來,其是由抽象類中的deliverResponse()返回,其代碼如下所示:

    @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }

而在 RequestQueue的初始化 中我們說過,BasicNetWork中的performRequest()方法是會去執(zhí)行Request的網(wǎng)絡(luò)請求,那它是要如何請求呢,這就需要我們將構(gòu)建的請求與ReqeustQueue及其相關(guān)組件關(guān)聯(lián)起來了,這就是我們下一步要介紹的RequestQueue的add()方法。

RequestQueue添加Request請求

在使用對應(yīng)的Request構(gòu)建好網(wǎng)絡(luò)請求后,RequestQueue會調(diào)用其add()方法將構(gòu)建的請求加入到請求隊(duì)列中,其具體執(zhí)行方法代碼如下所示:

// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
    mNetworkQueue.add(request);
    return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
    String cacheKey = request.getCacheKey();
    if (mWaitingRequests.containsKey(cacheKey)) {
        // There is already a request in flight. Queue up.
        Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
        if (stagedRequests == null) {
            stagedRequests = new LinkedList<Request<?>>();
        }
        stagedRequests.add(request);
        mWaitingRequests.put(cacheKey, stagedRequests);
        if (VolleyLog.DEBUG) {
            VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
        }
    } else {
        // Insert 'null' queue for this cacheKey, indicating there is now a request in
        // flight.
        mWaitingRequests.put(cacheKey, null);
        mCacheQueue.add(request);
    }
    return request;
}

從代碼中可以看到,如果Request不允許緩存的話,Request直接加入到NetWorkQueue中就直接return了,而默認(rèn)request是允許使用緩存的,當(dāng)允許使用緩存時,如果WaitingRequestsQueue中沒有相同的請求任務(wù),則會將任務(wù)key加入到waitingRquests中,但是傳入隊(duì)列為空,然后將任務(wù)加入CacheQueue中。如果已經(jīng)有同樣的請求在運(yùn)行,則會將新任務(wù)構(gòu)建隊(duì)列,加入到WaitingRequest中,但并不不執(zhí)行。

將Request加入到CacheQueue中后,我們留意到CacheQueue后續(xù)沒有操作了,這個原因在于,CacheQueue與前文中描述的CacheDispatcher耦合,CacheDispatcher作為一個線程死循環(huán),在RequestQueue中已經(jīng)啟動,它會不停的遍歷CacheQueue中的Request請求。其部分主要代碼如下所示:

// 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.
    mNetworkQueue.put(request);
    continue;
}

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

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

如果緩存未命中或者緩存過期,則會調(diào)用NetworkQueue進(jìn)行請求。如果緩存命中,則會直接調(diào)用ResponseDelivery(默認(rèn)為其實(shí)現(xiàn)類ExecutorDelivery)進(jìn)行結(jié)果分發(fā)。對于NetworkQueue.post(Request)的執(zhí)行結(jié)果,最后會由NetworkDispatcher去執(zhí)行,最后也會將執(zhí)行結(jié)果通過ResponseDelivery調(diào)用postResponse(reqeust, response)將結(jié)果返回(特別的對于reponse的結(jié)果CacheDispatcher和NetworkDispathcer都會調(diào)用Request.ParseNetworkResponse()方法來解析Response結(jié)果。這樣處理的結(jié)果就是,在回調(diào)listenter中,Response回調(diào)的就是已經(jīng)處理過的類型,而不用再去做一次數(shù)據(jù)解析,相關(guān)示例會在對于圖片請求源碼分析中進(jìn)行說明)。

Volley Request的請求回調(diào)

在ExecutorDelivery調(diào)用postResponse方法后,ExecutorDelivery將以handler.post(runnable)的形式來執(zhí)行調(diào)用的request與response,其關(guān)鍵構(gòu)建方法如下:

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

其中,ExecutorDelivery調(diào)用的postResponse方法中所執(zhí)行的Runnable對象為內(nèi)部類ResponseDeliveryRunnable,其執(zhí)行工作主要是將結(jié)果回調(diào)到Request中,其關(guān)鍵代碼如下所示:

// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
    mRequest.deliverResponse(mResponse.result);
} else {
    mRequest.deliverError(mResponse.error);
}

在回調(diào)到Request中后,最后會通過祖冊的listener將result回調(diào)回來,以StringRequest如下所示:

@Override
protected void deliverResponse(String response) {
    mListener.onResponse(response);
}

這樣一個完整的Volley 網(wǎng)絡(luò)請求也就完成了。最后附上一張簡單的流程圖,隨著分析完善,圖中缺失的部分也會慢慢補(bǔ)齊

簡單圖例

對于本文中的描述有什么疑問或者建議都可以留言提出,歡迎大家批評指導(dǎo)。O(∩_∩)O哈哈哈~

對于Volley的初步源碼分析我們也暫告一段落,在這篇文章中所遺留的Volley的異步圖片加載支持、緩存策略、Request子類分類請求區(qū)別以及Volley采用OkHttp作為網(wǎng)絡(luò)傳輸層等都會在后面文章繼續(xù)分析。

對于圖片加載的源碼分析已經(jīng)更新,詳情點(diǎn)擊:

對于網(wǎng)絡(luò)請求緩存的源碼分析已經(jīng)更新,詳情點(diǎn)擊:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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