從源碼解析Volley(一)

轉(zhuǎn)載劉望舒的博客Android網(wǎng)絡(luò)編程(四)從源碼解析Volley

1.Volley結(jié)構(gòu)圖

從上圖可以看到Volley分為三個(gè)線程师逸,分別是主線程蹈垢、緩存調(diào)度線程哆料、和網(wǎng)絡(luò)調(diào)度線程棋枕,首先請(qǐng)求會(huì)加入緩存隊(duì)列,如果發(fā)現(xiàn)可以找到相應(yīng)的緩存結(jié)果就直接讀取緩存并解析息楔,然后回調(diào)給主線程寝贡;如果在緩存中沒有找到結(jié)果,則將這條請(qǐng)求加入到網(wǎng)絡(luò)隊(duì)列中值依,然后發(fā)送HTTP請(qǐng)求兔甘,解析響應(yīng)并寫入緩存,并回調(diào)給主線程鳞滨。

2.從RequestQueue入手
我們都知道使用Volley之前首先要?jiǎng)?chuàng)建RequestQueue:

RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext());

這也是volley運(yùn)作的入口,看看newRequestQueue:

 public static RequestQueue newRequestQueue(Context context) { 
 return newRequestQueue(context, (HttpStack)null); 
}
public static RequestQueue newRequestQueue(Context context, HttpStack stack){ 
   return newRequestQueue(context, stack, -1);
 }

連續(xù)調(diào)用了兩個(gè)重載函數(shù)蟆淀,最終調(diào)用的是:

public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) { 
File cacheDir = new File(context.getCacheDir(), "volley"); 
String userAgent = "volley/0";
 try { 
String network = context.getPackageName(); 
PackageInfo queue = context.getPackageManager().getPackageInfo(network, 0); 
userAgent = network + "/" + queue.versionCode;
 } 
catch (NameNotFoundException var7) { 
; 
} 
if(stack == null) { 
if(VERSION.SDK_INT >= 9) { 
stack = new HurlStack(); } 
else { 
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
 }
 }
 BasicNetwork network1 = new BasicNetwork((HttpStack)stack); 
RequestQueue queue1; 
if(maxDiskCacheBytes <= -1) { 
queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1); 
} else { 
queue1 = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network1); 
} 
queue1.start(); return queue1;
 }

可以看到如果Android版本大于等于2.3則調(diào)用基于HttpURLConnection的HurlStack拯啦,否則就調(diào)用基于HttpClient的HttpClientStack。并創(chuàng)建了RequestQueue熔任,調(diào)用了start()方法:

public void start() {
 this.stop();
 this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery); 
 this.mCacheDispatcher.start();
 for(int i = 0; i < this.mDispatchers.length; ++i) {
 NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery); this.mDispatchers[i] = networkDispatcher;
 networkDispatcher.start();
 }
 }

CacheDispatcher是緩存調(diào)度線程褒链,并調(diào)用了start()方法,在循環(huán)中調(diào)用了NetworkDispatcher的start()方法疑苔,NetworkDispatcher是網(wǎng)絡(luò)調(diào)度線程甫匹,默認(rèn)情況下mDispatchers.length為4,默認(rèn)開啟了4個(gè)網(wǎng)絡(luò)調(diào)度線程惦费,也就是說有5個(gè)線程在后臺(tái)運(yùn)行并等待請(qǐng)求的到來兵迅。接下來我們創(chuàng)建各種的Request,并調(diào)用RequestQueue的add()方法:

 public <T> Request<T> add(Request<T> request) {
    request.setRequestQueue(this);
    Set var2 = this.mCurrentRequests; 
    synchronized(this.mCurrentRequests) { 
    this.mCurrentRequests.add(request);
    }
   request.setSequence(this.getSequenceNumber());
   request.addMarker("add-to-queue"); //如果不能緩存薪贫,則將請(qǐng)求添加到網(wǎng)絡(luò)請(qǐng)求隊(duì)列中
   if(!request.shouldCache()) { 
      this.mNetworkQueue.add(request);
      return request;
    } else {
            Map var8 = this.mWaitingRequests; 
            synchronized(this.mWaitingRequests) {
            String cacheKey = request.getCacheKey();
 //之前是否有執(zhí)行相同的請(qǐng)求且還沒有返回結(jié)果的恍箭,如果有的話將此請(qǐng)求加入mWaitingRequests隊(duì)列,不再重復(fù)請(qǐng)求 
           if(this.mWaitingRequests.containsKey(cacheKey)) {
            Object stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey); 
           if(stagedRequests == null) {
             stagedRequests = new LinkedList();
           }
           ((Queue)stagedRequests).add(request);
           this.mWaitingRequests.put(cacheKey, stagedRequests); 
          if(VolleyLog.DEBUG) {
          VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", new Object[]{cacheKey});
             }
          } else { //沒有的話就將請(qǐng)求加入緩存隊(duì)列mCacheQueue瞧省,同時(shí)加入mWaitingRequests中用來做下次同樣請(qǐng)求來時(shí)的重復(fù)判斷依據(jù) 
           this.mWaitingRequests.put(cacheKey, (Object)null); this.mCacheQueue.add(request);
          }
         return request;
       }
    }
 }


通過判斷request.shouldCache()扯夭,來判斷是否可以緩存,默認(rèn)是可以緩存的鞍匾,如果不能緩存交洗,則將請(qǐng)求添加到網(wǎng)絡(luò)請(qǐng)求隊(duì)列中,如果能緩存就判斷之前是否有執(zhí)行相同的請(qǐng)求且還沒有返回結(jié)果的橡淑,如果有的話將此請(qǐng)求加入mWaitingRequests隊(duì)列构拳,不再重復(fù)請(qǐng)求;沒有的話就將請(qǐng)求加入緩存隊(duì)列mCacheQueue,同時(shí)加入mWaitingRequests中用來做下次同樣請(qǐng)求來時(shí)的重復(fù)判斷依據(jù)隐圾。
從上面可以看出RequestQueue的add()方法并沒有做什么請(qǐng)求網(wǎng)絡(luò)或者對(duì)緩存進(jìn)行操作伍掀。當(dāng)將請(qǐng)求添加到網(wǎng)絡(luò)請(qǐng)求隊(duì)列或者緩存隊(duì)列時(shí),這時(shí)在后臺(tái)的網(wǎng)絡(luò)調(diào)度線程和緩存調(diào)度線程輪詢各自的請(qǐng)求隊(duì)列發(fā)現(xiàn)有請(qǐng)求任務(wù)則開始執(zhí)行暇藏,我們先看看緩存調(diào)度線程蜜笤。

3.CacheDispatcher緩存調(diào)度線程
CacheDispatcher的run()的方法:

public void run() {
        if(DEBUG) {
            VolleyLog.v("start new dispatcher", new Object[0]);
        }
        //線程優(yōu)先級(jí)設(shè)置為最高級(jí)別
        Process.setThreadPriority(10);
        this.mCache.initialize();

        while(true) {
            while(true) {
                while(true) {
                    while(true) {
                        try {
                            //獲取緩存隊(duì)列中的一個(gè)請(qǐng)求
                            final Request e = (Request)this.mCacheQueue.take();
                            e.addMarker("cache-queue-take");
                            //如果請(qǐng)求取消了則將請(qǐng)求停止掉
                            if(e.isCanceled()) {
                                e.finish("cache-discard-canceled");
                            } else {
                                //查看是否有緩存的響應(yīng)
                                Entry entry = this.mCache.get(e.getCacheKey());
                                //如果緩存響應(yīng)為空,則將請(qǐng)求加入網(wǎng)絡(luò)請(qǐng)求隊(duì)列
                                if(entry == null) {
                                    e.addMarker("cache-miss");
                                    this.mNetworkQueue.put(e);
                                    //判斷緩存響應(yīng)是否過期    
                                } else if(!entry.isExpired()) {
                                    e.addMarker("cache-hit");
                                    //對(duì)數(shù)據(jù)進(jìn)行解析并回調(diào)給主線程
                                    Response response = e.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
                                    e.addMarker("cache-hit-parsed");
                                    if(!entry.refreshNeeded()) {
                                        this.mDelivery.postResponse(e, response);
                                    } else {
                                        e.addMarker("cache-hit-refresh-needed");
                                        e.setCacheEntry(entry);
                                        response.intermediate = true;
                                        this.mDelivery.postResponse(e, response, new Runnable() {
                                            public void run() {
                                                try {
                                                    CacheDispatcher.this.mNetworkQueue.put(e);
                                                } catch (InterruptedException var2) {
                                                    ;
                                                }

                                            }
                                        });
                                    }
                                } else {
                                    e.addMarker("cache-hit-expired");
                                    e.setCacheEntry(entry);
                                    this.mNetworkQueue.put(e);
                                }
                            }
                        } catch (InterruptedException var4) {
                            if(this.mQuit) {
                                return;
                            }
                        }
                    }
                }
            }
        }
    }

    static {
        DEBUG = VolleyLog.DEBUG;
    }




看到四個(gè)while循環(huán)有些暈吧盐碱,讓我們挑重點(diǎn)的說把兔,首先從緩存隊(duì)列取出請(qǐng)求,判斷是否請(qǐng)求是否被取消了瓮顽,如果沒有則判斷該請(qǐng)求是否有緩存的響應(yīng)县好,如果有并且沒有過期則對(duì)緩存響應(yīng)進(jìn)行解析并回調(diào)給主線程。接下來看看網(wǎng)絡(luò)調(diào)度線程暖混。

4.NetworkDispatcher網(wǎng)絡(luò)調(diào)度線程
NetworkDispatcher的run()方法:

    public void run() {
        Process.setThreadPriority(10);

        while(true) {
            long startTimeMs;
            Request request;
            while(true) {
                startTimeMs = SystemClock.elapsedRealtime();

                try {
                    //從隊(duì)列中取出請(qǐng)求
                    request = (Request)this.mQueue.take();
                    break;
                } catch (InterruptedException var6) {
                    if(this.mQuit) {
                        return;
                    }
                }
            }

            try {
                request.addMarker("network-queue-take");
                if(request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                } else {
                    this.addTrafficStatsTag(request);
                    //請(qǐng)求網(wǎng)絡(luò)
                    NetworkResponse e = this.mNetwork.performRequest(request);
                    request.addMarker("network-http-complete");
                    if(e.notModified && request.hasHadResponseDelivered()) {
                        request.finish("not-modified");
                    } else {
                        Response volleyError1 = request.parseNetworkResponse(e);
                        request.addMarker("network-parse-complete");
                        if(request.shouldCache() && volleyError1.cacheEntry != null) {
                            //將響應(yīng)結(jié)果存入緩存
                            this.mCache.put(request.getCacheKey(), volleyError1.cacheEntry);
                            request.addMarker("network-cache-written");
                        }

                        request.markDelivered();
                        this.mDelivery.postResponse(request, volleyError1);
                    }
                }
            } catch (VolleyError var7) {
                var7.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                this.parseAndDeliverNetworkError(request, var7);
            } catch (Exception var8) {
                VolleyLog.e(var8, "Unhandled exception %s", new Object[]{var8.toString()});
                VolleyError volleyError = new VolleyError(var8);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                this.mDelivery.postError(request, volleyError);
            }
        }
    }

網(wǎng)絡(luò)調(diào)度線程也是從隊(duì)列中取出請(qǐng)求并且判斷是否被取消了缕贡,如果沒取消就去請(qǐng)求網(wǎng)絡(luò)得到響應(yīng)并回調(diào)給主線程。請(qǐng)求網(wǎng)絡(luò)時(shí)調(diào)用this.mNetwork.performRequest(request)拣播,這個(gè)mNetwork是一個(gè)接口晾咪,實(shí)現(xiàn)它的類是BasicNetwork,我們來看看BasicNetwork的performRequest()方法:

  public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();

        while(true) {
            HttpResponse httpResponse = null;
            Object responseContents = null;
            Map responseHeaders = Collections.emptyMap();

            try {
                HashMap e = new HashMap();
                this.addCacheHeaders(e, request.getCacheEntry());
                httpResponse = this.mHttpStack.performRequest(request, e);
                StatusLine statusCode1 = httpResponse.getStatusLine();
                int networkResponse1 = statusCode1.getStatusCode();
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                if(networkResponse1 == 304) {
                    Entry requestLifetime2 = request.getCacheEntry();
                    if(requestLifetime2 == null) {
                        return new NetworkResponse(304, (byte[])null, responseHeaders, true, SystemClock.elapsedRealtime() - requestStart);
                    }

                    requestLifetime2.responseHeaders.putAll(responseHeaders);
                    return new NetworkResponse(304, requestLifetime2.data, requestLifetime2.responseHeaders, true, SystemClock.elapsedRealtime() - requestStart);
                }


                ...省略


從上面可以看到在12行調(diào)用的是HttpStack的performRequest()方法請(qǐng)求網(wǎng)絡(luò)贮配,接下來根據(jù)不同的響應(yīng)狀態(tài)碼來返回不同的NetworkResponse谍倦。另外HttpStack也是一個(gè)接口,實(shí)現(xiàn)它的兩個(gè)類我們?cè)谇懊嬉呀?jīng)提到了就是HurlStack和HttpClientStack泪勒。讓我們?cè)倩氐絅etworkDispatcher昼蛀,請(qǐng)求網(wǎng)絡(luò)后,會(huì)將響應(yīng)結(jié)果存在緩存中圆存,如果響應(yīng)結(jié)果成功則調(diào)用this.mDelivery.postResponse(request, volleyError1)來回調(diào)給主線程叼旋。來看看Delivery的postResponse()方法:


    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, runnable));
    }

來看看ResponseDeliveryRunnable里面做了什么:

 private class ResponseDeliveryRunnable implements Runnable {
        private final Request mRequest;
        private final Response mResponse;
        private final Runnable mRunnable;

        public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
            this.mRequest = request;
            this.mResponse = response;
            this.mRunnable = runnable;
        }

        public void run() {
            if(this.mRequest.isCanceled()) {
                this.mRequest.finish("canceled-at-delivery");
            } else {
                if(this.mResponse.isSuccess()) {
                    this.mRequest.deliverResponse(this.mResponse.result);
                } else {
                    this.mRequest.deliverError(this.mResponse.error);
                }

                if(this.mResponse.intermediate) {
                    this.mRequest.addMarker("intermediate-response");
                } else {
                    this.mRequest.finish("done");
                }

                if(this.mRunnable != null) {
                    this.mRunnable.run();
                }

            }
        }
    }



第17行調(diào)用了this.mRequest.deliverResponse(this.mResponse.result),這個(gè)就是實(shí)現(xiàn)Request抽象類必須要實(shí)現(xiàn)的方法沦辙,我們來看看StringRequest的源碼:

public class StringRequest extends Request<String> {
      private final Listener<String> mListener;

      public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) {
          super(method, url, errorListener);
          this.mListener = listener;
      }

      public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
          this(0, url, listener, errorListener);
      }

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

      ...省略
  }

在deliverResponse方法中調(diào)用了this.mListener.onResponse(response)送淆,最終將response回調(diào)給了Response.Listener的onResponse()方法。我們用StringRequest請(qǐng)求網(wǎng)絡(luò)的寫法是這樣的:

   RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext());
    StringRequest mStringRequest = new StringRequest(Request.Method.GET, "http://www.baidu.com",
            new Response.Listener<String>() {
                @Override
                public void onResponse(String response) {
                    Log.i("wangshu", response);
                }
            }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e("wangshu", error.getMessage(), error);
        }
    });
    //將請(qǐng)求添加在請(qǐng)求隊(duì)列中
    mQueue.add(mStringRequest);
    



最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怕轿,一起剝皮案震驚了整個(gè)濱河市偷崩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌撞羽,老刑警劉巖阐斜,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異诀紊,居然都是意外死亡谒出,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笤喳,“玉大人为居,你說我怎么就攤上這事∩苯疲” “怎么了蒙畴?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)呜象。 經(jīng)常有香客問我膳凝,道長(zhǎng),這世上最難降的妖魔是什么恭陡? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任蹬音,我火速辦了婚禮,結(jié)果婚禮上休玩,老公的妹妹穿的比我還像新娘著淆。我一直安慰自己,他們只是感情好拴疤,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布牧抽。 她就那樣靜靜地躺著,像睡著了一般遥赚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阐肤,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天凫佛,我揣著相機(jī)與錄音,去河邊找鬼孕惜。 笑死愧薛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的衫画。 我是一名探鬼主播毫炉,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼削罩!你這毒婦竟也來了瞄勾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤弥激,失蹤者是張志新(化名)和其女友劉穎进陡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體微服,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趾疚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糙麦。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辛孵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赡磅,到底是詐尸還是另有隱情魄缚,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布仆邓,位于F島的核電站鲜滩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏节值。R本人自食惡果不足惜徙硅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搞疗。 院中可真熱鬧嗓蘑,春花似錦、人聲如沸匿乃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽幢炸。三九已至泄隔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宛徊,已是汗流浹背佛嬉。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闸天,地道東北人暖呕。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像苞氮,于是被迫代替她去往敵國和親湾揽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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