Android Volley源碼分析(一)


volley是一個非常流行的Android開源框架伸眶,自己平時也經(jīng)常使用它,但自己對于它的內(nèi)部的實現(xiàn)過程并沒有進行太多的深究灸撰。所以為了以后能更通透的使用它伞插,了解它的實現(xiàn)是一個非常重要的過程罚缕。自己有了一點研究艇纺,做個筆記同時與大家一起分享。期間自己也畫了一張圖怕磨,希望能更好的幫助我們理解其中的步驟與原理喂饥。如下:

Volley框架圖

開始看可能會一臉懵逼,我們先結(jié)合源碼一步一步來肠鲫,現(xiàn)在讓我們一起進入Volley的源碼世界员帮,來解讀大神們的編程思想。

newRequestQueue

如果使用過Volley的都知道导饲,不管是進行網(wǎng)絡(luò)請求還是什么別的圖片加載捞高,首先都要創(chuàng)建好一個RequestQueue

public static final RequestQueue volleyQueue = Volley.newRequestQueue(App.mContext);

RequestQueue的創(chuàng)建自然離不開Volley中的靜態(tài)方法newRequestQueue,從上面的圖片也能知道首先進入點是newRequestQueue,好了現(xiàn)在我們來看下該方法中到底做了什么:

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        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 {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        Network network = new BasicNetwork(stack);
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();
        return queue;
    }    

從上面的源碼中我們能發(fā)現(xiàn)有一個stack渣锦,它代表著網(wǎng)絡(luò)請求通信HttpClientHttpUrlConnection硝岗,但我們一般都是默認設(shè)置為null。因為它會默認幫我們進判斷選擇更適合的袋毙。當手機版本大于等于9時會使用HurlStack它里面使用HttpUrlConnection進行實現(xiàn)通信的型檀,而小于9時則創(chuàng)建HttpClientStack,它里面自然是HttpClient的實現(xiàn)听盖。通過BasicNetwork構(gòu)造成Network來進行傳遞使用胀溺;在這之后構(gòu)建了RequestQueue裂七,其中幫我們構(gòu)造了一個緩存new DiskBasedCache(cacheDir),默認為5MB,緩存目錄為volley仓坞。所以我們能在data/data/應(yīng)用包名/volley下找到緩存背零。最后調(diào)用其start方法,并返回RequestQueue无埃。這就是我們前面第一段代碼的內(nèi)部實現(xiàn)徙瓶。下面我們進入RequestQueue中的start方法看下它到底做了什么呢?

RequestQueue

RequestQueue中通過this調(diào)用自身來默認幫我們調(diào)用了下面的構(gòu)造函數(shù)

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

cache后續(xù)在NetworkDispatcher中會幫我們進行response.cacheEntry的緩存嫉称,netWork是前面的根據(jù)版本所封裝的通信侦镇,threadPoolSize線程池大小默認為4delivery,是ExecutorDelivery作用在主線程,在最后對請求響應(yīng)的分發(fā)澎埠。

start

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方法中我們發(fā)現(xiàn)它實現(xiàn)了兩個dispatch,一個是CacheDispatcher緩存派遣,另一個是networkDispatcher進行網(wǎng)絡(luò)派遣虽缕,其實他們都是Thread,所以都調(diào)用了他們的start方法蒲稳。其中networkDispatcher默認構(gòu)建了4個,相當于包含4個線程的線程池∥榕桑現(xiàn)在我們先不去看他們內(nèi)部的run方法到底實現(xiàn)了什么江耀,我們還是接著看RequestQueue中我們頻繁使用的add方法。

add

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.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }
        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");
        // 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;
        }
    }

我們結(jié)合代碼與前面的圖诉植,首先會為當前的request設(shè)置RequestQueue,并且根據(jù)情況同步是否是當前正在進行的請求祥国,加入到mCurrentRequests中,看11行代碼晾腔,之后對當前request進行判斷是否需要緩存(默認實現(xiàn)是true舌稀,如果不需要可調(diào)用request.setShouldCache()進行設(shè)置),如果不需要則直接加入到前面的mNetworkQueue中,它會在CacheDispatcherNetworkDispatcher中做相應(yīng)的處理灼擂,然后返回request壁查。如果需要緩存,看16行代碼剔应,則對mWaitingRequests中是否包含cacheKey進行相應(yīng)的處理睡腿。其中cacheKey為請求的url。最后再加入到緩存隊列mCacheQueue中峻贮。

finish

細心的人會發(fā)現(xiàn)當對應(yīng)cacheKeyvalue不為空時席怪,創(chuàng)建了LinkedListQueue,只是將request加入到了Queue中,只是更新了mWaitingRequests中相應(yīng)的value但并沒有加入到mCacheQueue中纤控。其實不然挂捻,因為后續(xù)會調(diào)用finish方法,我們來看下源碼:

<T> void finish(Request<T> request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }
        synchronized (mFinishedListeners) {
          for (RequestFinishedListener<T> listener : mFinishedListeners) {
            listener.onRequestFinished(request);
          }
        }
        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    if (VolleyLog.DEBUG) {
                        VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
                                waitingRequests.size(), cacheKey);
                    }
                    // Process all queued up requests. They won't be considered as in flight, but
                    // that's not a problem as the cache has been primed by 'request'.
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }

1422行代碼船万,正如上面我所說刻撒,會將Queue中的request全部加入到mCacheQueue中惜辑。
好了RequestQueue的主要源碼差不多就這些,下面我們進入CacheDispatcher的源碼分析,看它究竟如何工作的呢疫赎?

CacheDispatcher

前面提到了它與NetworkDispatcher本質(zhì)都是Thread盛撑,那么我們自然是看run方法

run

@Override
    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();
                
        while (true) {
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");
                
                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }
                
                // 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);
                } 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;
             
                    // 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) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }
                                                                                          
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }

看起來很多,我們結(jié)合圖來挑主要的看捧搞。首先創(chuàng)建了一個無限循環(huán)一直在監(jiān)視著request的變化抵卫。從緩存隊列mCacheQueue中獲取request,如果該請求是cancle了,調(diào)用request.finish()清除相應(yīng)數(shù)據(jù)并進行下一個請求的操作胎撇,否則從前面提到的mCache中獲取Cache.Entry介粘。如果不存在或者已經(jīng)過期,將請求加入到網(wǎng)絡(luò)隊列中mNetWorkQueue晚树,進行后續(xù)的網(wǎng)絡(luò)請求姻采。如果存在(41行代碼)則進行request.parseNetworkResponse()解析出response,不同的request對應(yīng)不同的解析方法。例如StringRequestJsonObjectRequest有各自的解析實現(xiàn)爵憎。再看45行慨亲,發(fā)現(xiàn)不管entry是否需要更新的,都會進一步對response進行mDelivery.postResponse(request, response)遞送宝鼓,不同的是需要更新的話重新設(shè)置requestentry與加入到mNetworkQueue中刑棵,也就相當與重新進行網(wǎng)絡(luò)請求一遍。那么再回到遞送的階段愚铡,前面已經(jīng)提到在創(chuàng)建RequestQueue是實現(xiàn)了ExecutorDelivery,mDelivery.postResponse就是其中的方法蛉签。我們來看一下

ExecutorDelivery

在這里創(chuàng)建了一個Executor,對后面進行遞送,作用在主線程

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

postResponse

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

這個方法就簡單了就是調(diào)用execute進行執(zhí)行沥寥,在進入ResponseDeliveryRunnablerun看它如何執(zhí)行

ResponseDeliveryRunnable

public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }
 
            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) {
                 mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }
 
            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }
 
            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
       }

主要是第9行代碼碍舍,對于不同的響應(yīng)做不同的遞送,deliverResponsedeliverError內(nèi)部分別調(diào)用的就是我們非常熟悉的Listener中的onResponseonErrorResponse方法邑雅,進而返回到我們對網(wǎng)絡(luò)請求結(jié)果的處理函數(shù)片橡。

這就是整個的緩存派遣,簡而言之蒂阱,存在請求響應(yīng)的緩存數(shù)據(jù)就不進行網(wǎng)絡(luò)請求锻全,直接調(diào)用緩存中的數(shù)據(jù)進行分發(fā)遞送。反之執(zhí)行網(wǎng)絡(luò)請求录煤。
下面我來看下NetworkDispatcher是如何處理的

NetworkDispatcher

run

@Override
    public void run() {
         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // Take a request from the queue.
                request = mQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
 
            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");
                    continue;
                }
 
                addTrafficStatsTag(request);
 
                // Perform the network request.
                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");
                    continue;
                }
 
                // 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);
            } catch (VolleyError volleyError) {
                 volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } 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);
            }
        }
    }

在這里也創(chuàng)建了一個無限循環(huán)的while,同樣也是先獲取request鳄厌,不過是從mQueue中,即前面多次出現(xiàn)的mNetWorkQueue,通過看代碼發(fā)現(xiàn)一些實現(xiàn)跟CacheDispatcher中的類似妈踊。也正如圖片中所展示的一樣了嚎,(23行)如何請求取消了,直接finish;否則進行網(wǎng)絡(luò)請求歪泳,調(diào)用(31行)mNetwork.performRequest(request)萝勤,這里的mNetWork即為前面RequestQueue中對不同版本進行選擇的stack的封裝,分別調(diào)用HurlStackHttpClientStack各自的performRequest方法呐伞,該方法中構(gòu)造請求頭與參數(shù)分別使用HttpClient或者HttpUrlConnection進行網(wǎng)絡(luò)請求敌卓。我們再來看42行,是不是很熟悉伶氢,與CacheDispatcher中的一樣進行response進行解析趟径,然后如果需要緩存就加入到緩存中,最后(54行)再調(diào)用mDelivery.postResponse(request, response)進行遞送癣防。至于后面的剩余的步驟與CacheDispatcher中的一模一樣蜗巧,這里就不多累贅了。

好了蕾盯,Volley的源碼解析先就到這里了幕屹,我們再回過去看那張圖是不是感覺很清晰了呢?

總結(jié)

我們來對使用Volley網(wǎng)絡(luò)請求做個總結(jié)

  • 通過newRequestQueue初始化與構(gòu)造RequestQueue
  • 調(diào)用RequestQueue中的add方法添加request到請求隊列中
  • 緩存派遣,先進行CacheDispatcher,判斷緩存中是否存在级遭,有則解析response望拖,再直接postResponse遞送,否則進行后續(xù)的網(wǎng)絡(luò)請求
  • 網(wǎng)絡(luò)派遣装畅,NetworkDispatcher中進行相應(yīng)的request請求靠娱,解析response如設(shè)置了緩存就將結(jié)果保存到cache中,再進行最后的postResponse遞送掠兄。

如果有所幫助歡迎關(guān)注我的下一次解析
更多分享:個人博客

關(guān)注

怪談時間到了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锌雀,隨后出現(xiàn)的幾起案子蚂夕,更是在濱河造成了極大的恐慌,老刑警劉巖腋逆,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婿牍,死亡現(xiàn)場離奇詭異,居然都是意外死亡惩歉,警方通過查閱死者的電腦和手機等脂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撑蚌,“玉大人上遥,你說我怎么就攤上這事≌浚” “怎么了粉楚?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我模软,道長伟骨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任燃异,我火速辦了婚禮携狭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘回俐。我一直安慰自己逛腿,他們只是感情好,可當我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布鲫剿。 她就那樣靜靜地躺著鳄逾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灵莲。 梳的紋絲不亂的頭發(fā)上雕凹,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天,我揣著相機與錄音政冻,去河邊找鬼枚抵。 笑死,一個胖子當著我的面吹牛明场,可吹牛的內(nèi)容都是我干的汽摹。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼苦锨,長吁一口氣:“原來是場噩夢啊……” “哼逼泣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起舟舒,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤拉庶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秃励,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氏仗,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年夺鲜,在試婚紗的時候發(fā)現(xiàn)自己被綠了皆尔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡币励,死狀恐怖慷蠕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榄审,我是刑警寧澤砌们,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響浪感,放射性物質(zhì)發(fā)生泄漏昔头。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一影兽、第九天 我趴在偏房一處隱蔽的房頂上張望揭斧。 院中可真熱鬧,春花似錦峻堰、人聲如沸讹开。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旦万。三九已至,卻和暖如春镶蹋,著一層夾襖步出監(jiān)牢的瞬間成艘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工贺归, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淆两,地道東北人。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓拂酣,卻偏偏與公主長得像秋冰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子婶熬,可洞房花燭夜當晚...
    茶點故事閱讀 43,666評論 2 350

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