1. 程式人生 > >為什麼說Volley適合資料量小,通訊頻繁的網路操作

為什麼說Volley適合資料量小,通訊頻繁的網路操作

前言

網路程式設計對於客戶端來說是一塊及其重要的地方,使用高效的網路請求框架將為你的系統產生很大的影響。而Volley作為谷歌的一個開源專案,炙手可熱。有很多中小型公司的安卓移動客戶端的網路程式都是基於volley的。
Volley的優點很多,光可擴充套件性這一條優點就值得我們稱讚。但是我想針對的是在 Google I/O 2013 大會上釋出Volley的時候的一句話:a burst or emission of many things or a large amount at once(爆炸性的事件在很短的時間內發生),意思就是:Volley特別適合資料量小,通訊量大的客戶端。同為網路請求框架,為什麼Volley會有這樣的特點?接下來,我就用我的理解來解釋一下。
ByteArrayPool
ByteArrayPool產生背景

根據類名,知道這是一個位元組陣列快取池。沒錯,就是用來快取 網路請求獲得的資料。
當網路請求得到返回資料以後,我們需要在記憶體開闢出一塊區域來存放我們得到的網路資料,不論是json還是圖片,都會存在於記憶體的某一塊區域,然後拿到UI顯示,然而客戶端請求是相當頻繁的操作,想一下我們平時使用知乎等一些客戶端,幾乎每一個操作都要進行網路請求(雖然知乎大部分是WebView)。那麼問題來了:這麼頻繁的資料請求,獲得資料以後我們先要在堆記憶體開闢儲存空間,然後顯示,等到時機成熟,GC回收這塊區域,如此往復,那麼GC的負擔就相當的重,然而Android客戶端處理能力有限,頻繁GC對客戶端的效能有直接影響。我們能不能減少GC和記憶體的分配呢?我想這個問題就是這個類的產生背景。
實現原理(怎麼實現快取從而減少GC)

在ByteArrayPool中維護了兩個List集合。
屬性名 作用 型別
mBuffersByLastUse 按照最近使用對byte[]排序 LinkedList
mBuffersBySize 按照byte[]大小對byte[]排序 ArrayList

通過上述兩個屬性的作用可以分析出它們是ByteArrayPool類對byte[]的管理。
從緩衝區取空間

當請求資料返回以後,我們不是急於在記憶體開闢空間,而是從ByteArrayPool中取出一塊已經分配的記憶體區域。此時會呼叫ByteArrayPool的getBuf(int)方法,來得到一塊引數大小的區域,原始碼如下:

 public synchronized byte[] getBuf(int len) {
        for (int i = 0; i < mBuffersBySize.size(); i++) {
            byte[] buf = mBuffersBySize.get(i);
            if (buf.length >= len) {
                mCurrentSize -= buf.length;
                mBuffersBySize.remove(i);
                mBuffersByLastUse.remove(buf);
                return buf;
            }
        }
        return new byte[len];
    }

方法的第2行程式碼,遍歷mBuffersBySize,找到最適合len大小的byte[]。第6 ~8行更新快取池中資料的大小,並從兩個陣列中去除分配出去的byte[]。如果在快取池中沒有要求的byte[],此時會從記憶體分配一跨區域返回。
此方法主要的功能: 不必每次存資料都要進行記憶體分配,而是先查詢緩衝池中有無適合的記憶體區域,如果有,直接拿來用,從而減少記憶體分配的次數。
其實這個方法有改進空間:由於在類中有一個mSizeLimit屬性,表示此緩衝區的最大值。我們可以在方法體的第一行判斷 len與mSizeLimit的大小,如否 len>mSizeLimit,直接進入到最後一句執行,否則,迴圈。修改後的方法如下:

public synchronized byte[] getBuf(int len) {
        //有的條件不需迴圈
        if(len<=mSizeLimit)
        {
            for (int i = 0; i < mBuffersBySize.size(); i++) {
                byte[] buf = mBuffersBySize.get(i);
                if (buf.length >= len) {
                    mCurrentSize -= buf.length;
                    mBuffersBySize.remove(i);
                    mBuffersByLastUse.remove(buf);
                    return buf;
                }
            }
        }
        return new byte[len];
    }

將空間返回給快取池

如果只是拿資料,快取區的只會越來越小,我們還需要向緩衝區中加入儲存空間。這個時候涉及到一個方法:returnBuf(byte[])。

 public synchronized void returnBuf(byte[] buf) {
        if (buf == null || buf.length > mSizeLimit) {
            return;
        }
        mBuffersByLastUse.add(buf);
        int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
        if (pos < 0) {
            pos = -pos - 1;
        }
        mBuffersBySize.add(pos, buf);
        mCurrentSize += buf.length;
        trim();
    }

方法首先檢查 要插入的資料大小有沒有超出邊界,如果沒有,利用二分法找到插入位置,將資料插入到上述的兩個集合,完成排序。然後更新緩衝池的大小,以方便從緩衝區中取儲存空間。
結語

ByteArrayPool利用getBuf和returnBuf以及mBuffersByLastUse和mBuffersBySize完成位元組陣列的快取。當需要使記憶體區域的時候,先從已經分配的區域中獲得以減少記憶體分配次數。當空間用完以後,在將資料返回給此緩衝區。這樣,就會減少記憶體區域堆記憶體的波動和減少GC的回收,讓CPU把更多的效能留給頁面的渲染,提高效能。通過這個類發現,谷歌對技術的細節十分考究。