volley源碼學(xué)習(xí)

volley源碼學(xué)習(xí)

之前一直對(duì)于源碼學(xué)習(xí)抱著一種又愛又恨的心情。愛的是因?yàn)橹涝创a有一些特別好的設(shè)計(jì)思路赏半,可以讓自己借鑒未辆,而且對(duì)于設(shè)計(jì)模式來說是最好的實(shí)戰(zhàn)場燃辖。那為啥還會(huì)恨呢,曾經(jīng)很多次下載了很多開源庫的源碼,可是看的看的就感覺云里霧里师骗,不知所蹤历等。心中沒有一個(gè)總體的框架,總感覺看的細(xì)如牛毛辟癌,一葉障目寒屯。今天又找時(shí)間翻出最簡單的volley,準(zhǔn)備從頭再看一遍愿待。沒想到收獲很多浩螺,寫下這篇文章,用來記錄仍侥。

volley是什么

volley是一個(gè)封裝好的網(wǎng)絡(luò)庫要出,是把httpclient或HttpURLConnection又封裝了一層,加上了線程农渊,隊(duì)列患蹂,緩存等機(jī)制,讓網(wǎng)絡(luò)請(qǐng)求更加容易砸紊。

volley是google官方推出的一個(gè)開源項(xiàng)目传于,專門適用于android輕量的請(qǐng)求。但是對(duì)于大文件等支持不好醉顽。

volley整個(gè)源碼都是用了接口編程的思想沼溜,這也比較符合設(shè)計(jì)模式的優(yōu)秀實(shí)踐。

所以看volley源碼會(huì)對(duì)于接口編程有較深入的理解游添。

除了volley之外系草,還有一個(gè)retrofit網(wǎng)絡(luò)框架,是對(duì)okhttp的封裝,這個(gè)框架現(xiàn)在特別火唆涝,而且如果你想領(lǐng)略設(shè)計(jì)模式之美的話找都,這個(gè)retrofit源碼必須看,等之后再寫一篇分析retrofit的文章

volley工作流程

首先先放一張google官方給的流程圖廊酣,雖然比較簡單能耻,但是可以在心中有一個(gè)大致的概念

1.png

首先我們從volley調(diào)用入手,找到調(diào)用入口亡驰,這樣就可以按圖索驥晓猛,一點(diǎn)點(diǎn)摸索出整個(gè)的流程。

RequestQueue queue = Volley.newRequestQueue(this);
String url ="https://www.gaotenglife.com";

// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        // Display the first 500 characters of the response string.
        mTextView.setText("Response is: "+ response.substring(0,500));
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        mTextView.setText("That didn't work!");
    }
});
// Add the request to the RequestQueue.
queue.add(stringRequest);

從上面的代碼可以看出凡辱,我們首先通過newRequestQueue創(chuàng)建出一個(gè)requestqueue鞍帝,也就是請(qǐng)求隊(duì)列。然后把一個(gè)請(qǐng)求加入到請(qǐng)求隊(duì)列里面煞茫。這里可以看出帕涌,當(dāng)我們把stringrequest加入請(qǐng)求隊(duì)列后摄凡,請(qǐng)求便開始了,所以我們便從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;
    }
    mCacheQueue.add(request);
    return request;
 }

這里面主要的功能就是把網(wǎng)絡(luò)請(qǐng)求加入到對(duì)應(yīng)的網(wǎng)絡(luò)請(qǐng)求隊(duì)列(mNetworkQueue),如果設(shè)置了請(qǐng)求需要緩存的話亲澡,同時(shí)也加入到緩存隊(duì)列中。這里比較奇怪纫版,為啥只是單單加入隊(duì)列網(wǎng)絡(luò)請(qǐng)求就能發(fā)出去呢床绪。于是我們接著向下看,最有可能是在mNetworkQueue.add的時(shí)候其弊,發(fā)出請(qǐng)求癞己。于是我們繼續(xù)深入,發(fā)現(xiàn)mNetworkQueue也只是一個(gè)簡單的線程安全的隊(duì)列,也沒有做過多的操作梭伐。其實(shí)這樣也是合理的痹雅,隊(duì)列不應(yīng)該包含更多的業(yè)務(wù)邏輯在里面。于是我繼續(xù)查找糊识,是哪里持有了這個(gè)隊(duì)列绩社。

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

于是發(fā)現(xiàn)兩個(gè)地方使用了這個(gè)mNetworkQueue隊(duì)列,CacheDispatcher赂苗,NetworkDispatcher從名字是就能看出愉耙,最有可能就是NetworkDispatcher里面進(jìn)行網(wǎng)絡(luò)隊(duì)列的處理了。于是我們看NetworkDispatcher拌滋,發(fā)現(xiàn)這個(gè)NetworkDispatcher原來就是一個(gè)線程朴沿。

public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    while (true) {
        try {
            processRequest();
        } catch (InterruptedException e) {
            // We may have been interrupted because it was time to quit.
            if (mQuit) {
                return;
            }
        }
    }
}

在它的run方法里面,不斷循環(huán)調(diào)用processRequest败砂,在processRequest里面從請(qǐng)求隊(duì)列里取出request

Request<?> request = mQueue.take();//取出請(qǐng)求
......


 // Perform the network request.
        NetworkResponse networkResponse = mNetwork.performRequest(request);//通過networt真正去請(qǐng)求網(wǎng)絡(luò)

從這里看出赌渣,單一職責(zé)設(shè)計(jì)思想。隊(duì)列只處理隊(duì)列的邏輯吠卷,網(wǎng)絡(luò)只處理網(wǎng)絡(luò)的邏輯锡垄。不交叉寫到一起沦零。

那么network里面是如何真正請(qǐng)求網(wǎng)絡(luò)的呢祭隔,因?yàn)楸容^volley是封裝了具體網(wǎng)絡(luò)請(qǐng)求庫。
于是我們看到BasicNetwork里面路操,它里面有BaseHttpStack疾渴,而BaseHttpStack是個(gè)抽象類,它的子類屯仗,比如HurlStack是封裝了
HttpURLConnection搞坝,而HttpClientStack是封裝了httpcliet底層庫。這樣network就可以根據(jù)配置魁袜,選取我們需要的底層網(wǎng)絡(luò)庫桩撮。

那么請(qǐng)求完之后敦第,返回的結(jié)果如何回調(diào)回去呢。這里又借助ResponseDelivery店量,將請(qǐng)求的結(jié)果發(fā)送到ui線程

 mDelivery.postResponse(request, response);

我們再進(jìn)入ExecutorDelivery這里面

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

然后通過mResponsePoster這里面調(diào)用handler芜果,最后將結(jié)果發(fā)送給ui線程

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

這里最后調(diào)用了request里面設(shè)置的監(jiān)聽函數(shù),最后把數(shù)據(jù)給了調(diào)用方融师。

到此我們的一個(gè)調(diào)用過程就分析完畢了右钾。是不是挺簡單的。

緩存的實(shí)現(xiàn)

在剛剛上面分析代碼的時(shí)候說到旱爆,mNetworkQueue被兩個(gè)類持有舀射,一個(gè)就是咱們已經(jīng)分析過的NetworkDispatcher,還有一個(gè)就是咱們這一節(jié)要講的CacheDispatcher。

public <T> Request<T> add(Request<T> request)
    ...
    ...
    mCacheQueue.add(request);

在上面隊(duì)列add的時(shí)候怀伦,如果請(qǐng)求設(shè)置了需要緩存脆烟,request.shouldCache(),也就是這個(gè)為true空镜,那么會(huì)先將這個(gè)request加入到緩存的隊(duì)列里面浩淘。

而處理緩存隊(duì)列的,就是CacheDispatcher吴攒。和NetworkDispatcher類似张抄,這個(gè)CacheDispatcher也是一個(gè)線程,在線程的run方法里面洼怔,執(zhí)行了processRequest這個(gè)方法署惯。這里面主要處理了緩存相關(guān)邏輯

首先先從緩存隊(duì)列里面將緩存的request取出來。

final Request<?> request = mCacheQueue.take();

....省略非主要代碼
 // 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.
        if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
            mNetworkQueue.put(request);
        }
        return;
    }

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

然后進(jìn)行了兩個(gè)判斷镣隶,第一個(gè)就是先從緩存中獲取這個(gè)request极谊,如果沒有緩存,則將請(qǐng)求直接加入到之前的網(wǎng)絡(luò)隊(duì)列安岂,進(jìn)行網(wǎng)絡(luò)請(qǐng)求轻猖。如果有緩存,則再通過判斷entry.isExpired是否過期域那,如果緩存的request已經(jīng)過期咙边,則也加入到網(wǎng)絡(luò)請(qǐng)求的隊(duì)列中。

接下來次员,如果有緩存并且緩存沒有過期败许,則從緩存中取到之前請(qǐng)求過的數(shù)據(jù),并進(jìn)行解析淑蔚。如下

 Response<?> response = request.parseNetworkResponse(
            new NetworkResponse(entry.data, entry.responseHeaders));

之后又通過了一層判斷

 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;

        if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
            // 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) {
                        // Restore the interrupted status
                        Thread.currentThread().interrupt();
                    }
                }
            });
        } else {
            // request has been added to list of waiting requests
            // to receive the network response from the first request once it returns.
            mDelivery.postResponse(request, response);
        }
    }

也就是說市殷,如果緩存的數(shù)據(jù)需要刷新,那么還是需要將request發(fā)送給網(wǎng)絡(luò)隊(duì)列進(jìn)行請(qǐng)求刹衫。如果數(shù)據(jù)不需要刷新醋寝,則直接通過mDelivery將緩存的數(shù)據(jù)發(fā)送給ui線程搞挣。

這里面有兩個(gè)概念,如下

  /** True if the entry is expired. */
    public boolean isExpired() {
        return this.ttl < System.currentTimeMillis();
    }

    /** True if a refresh is needed from the original data source. */
    public boolean refreshNeeded() {
        return this.softTtl < System.currentTimeMillis();
    }

ttl和softttl這兩個(gè)是http協(xié)議里面通過header計(jì)算出來的兩個(gè)值音羞。詳細(xì)可以查看HttpHeaderParser這個(gè)類柿究。

重試策略

看到重試策略的時(shí)候,我首先自己想了下黄选,如果要我自己實(shí)現(xiàn)重試策略蝇摸,我會(huì)如何做呢,很直白的的思維就是办陷,在網(wǎng)絡(luò)請(qǐng)求失敗的時(shí)候貌夕,判斷是否有重試策略,然后在網(wǎng)絡(luò)請(qǐng)求失敗的地方民镜,重新發(fā)起網(wǎng)絡(luò)請(qǐng)求啡专。

所以我就一直按照這個(gè)思路去尋找,可以在volley網(wǎng)路失敗的地方制圈,我只找到了如下的代碼

private static void attemptRetryOnException(String logPrefix, Request<?> request,
        VolleyError exception) throws VolleyError {
    RetryPolicy retryPolicy = request.getRetryPolicy();
    int oldTimeout = request.getTimeoutMs();

    try {
        retryPolicy.retry(exception);
    } catch (VolleyError e) {
        request.addMarker(
                String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
        throw e;
    }
    request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
}

這個(gè)函數(shù)是所有網(wǎng)絡(luò)有有異常的時(shí)候们童,會(huì)調(diào)用的。但是我發(fā)現(xiàn)這里面除了設(shè)置了重試策略的一些屬性鲸鹦,其他沒有做網(wǎng)絡(luò)請(qǐng)求操作慧库。這就奇怪了。難道網(wǎng)絡(luò)請(qǐng)求會(huì)自己發(fā)起馋嗜。這讓我百思不得其解齐板。

于是我又一遍一遍看了網(wǎng)絡(luò)請(qǐng)求的代碼,終于發(fā)現(xiàn)了端倪

原來在networkdispater調(diào)用Network進(jìn)行網(wǎng)絡(luò)請(qǐng)求的時(shí)候葛菇,network里面竟然寫了一個(gè)while循環(huán)

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

這樣也就能解釋了甘磨,這個(gè)循環(huán)會(huì)一直嘗試去請(qǐng)求網(wǎng)絡(luò),直到不滿足重試策略之后眯停,退出循環(huán)济舆。也就是說,這個(gè)重試策略沒有正真參與具體了重試邏輯莺债。而只是保存了自己的重試狀態(tài)滋觉,真正的重試邏輯還是網(wǎng)絡(luò)請(qǐng)求去保證。
看到這里九府,我突然感覺到寫這樣代碼的人椎瘟,真是思路別具一格覆致。而且這樣的好處顯而易見侄旬,你可以重寫重試策略,而不需要重新修改網(wǎng)絡(luò)重試的邏輯煌妈。這也就是設(shè)計(jì)模式里面策略模式比較好的運(yùn)用吧儡羔。

結(jié)論體會(huì)

通過完整的看了一遍volley源碼宣羊,體會(huì)到了幾個(gè)比較重要的思想

  • 一個(gè)就是單一職責(zé),每一層都分開汰蜘,負(fù)責(zé)每一層應(yīng)該有的功能仇冯。互相解耦族操,底層不依賴上層苛坚。比如網(wǎng)絡(luò)請(qǐng)求Network這個(gè)類,不依賴與上層的隊(duì)列類,而具體封裝底層網(wǎng)絡(luò)請(qǐng)求的BaseHttpStack也不依賴上層Network色难∑貌眨可以讓network層很輕松的更換底層網(wǎng)絡(luò)請(qǐng)求庫。

  • 策略模式的運(yùn)用枷莉,也讓代碼邏輯與策略分離娇昙,策略里面不依賴具體邏輯。邏輯代碼里面通過改變不同策略對(duì)象笤妙,來達(dá)到控制不同策略的目的冒掌。

  • 面向接口的編程,整個(gè)volley各個(gè)模塊都是通過接口互相之間調(diào)用蹲盘,這樣不依賴與具體實(shí)現(xiàn)股毫,就將整個(gè)框架都搭建好了,感覺很受啟發(fā)召衔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末皇拣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子薄嫡,更是在濱河造成了極大的恐慌氧急,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毫深,死亡現(xiàn)場離奇詭異吩坝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)哑蔫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門钉寝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人闸迷,你說我怎么就攤上這事嵌纲。” “怎么了腥沽?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵逮走,是天一觀的道長。 經(jīng)常有香客問我今阳,道長师溅,這世上最難降的妖魔是什么茅信? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮墓臭,結(jié)果婚禮上蘸鲸,老公的妹妹穿的比我還像新娘。我一直安慰自己窿锉,他們只是感情好酌摇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嗡载,像睡著了一般妙痹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鼻疮,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天怯伊,我揣著相機(jī)與錄音,去河邊找鬼判沟。 笑死耿芹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的挪哄。 我是一名探鬼主播吧秕,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼迹炼!你這毒婦竟也來了砸彬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤斯入,失蹤者是張志新(化名)和其女友劉穎砂碉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刻两,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡增蹭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了磅摹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滋迈。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖户誓,靈堂內(nèi)的尸體忽然破棺而出饼灿,到底是詐尸還是另有隱情,我是刑警寧澤帝美,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布碍彭,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏硕旗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一女责、第九天 我趴在偏房一處隱蔽的房頂上張望漆枚。 院中可真熱鬧,春花似錦抵知、人聲如沸墙基。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽残制。三九已至,卻和暖如春掖疮,著一層夾襖步出監(jiān)牢的瞬間初茶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工浊闪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恼布,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓搁宾,卻偏偏與公主長得像折汞,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盖腿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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