1. 程式人生 > >Volley原始碼分析二

Volley原始碼分析二

在前兩天我釋出的文章:Volley原始碼分析一 中我較為詳細的分析了Volley,今天繼續,這篇文章會講一些上一篇沒有提到的比較細節的點,以及對於Volley原始碼中一些可以優化的實現的一些思考

ByteArrayPool的分析

byte[] 的回收池,用於 byte[] 的回收再利用,減少了記憶體的分配和回收。主要通過一個元素長度從小到大排序的ArrayList作為 byte[] 的快取,另有一個按使用時間先後排序的ArrayList屬性用於快取滿時清理元素。

public synchronized void returnBuf(byte[] buf)

將用過的 byte[] 回收,根據 byte[] 長度按照從小到大的排序將 byte[] 插入到快取中合適位置。

public synchronized byte[] getBuf(int len)

獲取長度不小於 len 的 byte[],遍歷快取,找出第一個長度大於傳入引數len的 byte[],並返回;如果最終沒有合適的 byte[],new 一個返回。

private synchronized void trim()

當快取的 byte 超過預先設定的大小時,按照先進先出的順序刪除最早的 byte[]。
上面的內容摘自:Volley原始碼解析

自定義請求

自定義請求的話就拿ImageRequest和ClearCacheRequest來講解好了,ClearCacheRequest是因為比較特別所以單獨拿出來講一講,而ImageRequest的話其實和StringRequest什麼的沒有本質上的區別,介紹了這個就知道其他怎麼寫了,代表了大部分請求的寫法,選擇它講是為了引出後面的內容,因為ImageRequest請求圖片還不是很好,只有磁碟快取的話是明顯不夠的。

ClearCacheRequest

用於人為清空 Http 快取的請求。
新增到 RequestQueue 後能很快執行,因為優先順序很高,為Priority.IMMEDIATE。並且清空快取的方法mCache.clear()寫在了isCanceled()方法體中,能最早的得到執行。但是我看的一些文章中都說這個清理快取的請求的寫法不是那麼好,大家就稍微注意一些這一條請求就行,至於其中的程式碼,沒什麼亮點可介紹。

ImageRequest

首先從他的建構函式開始,最完整的構造方法(所有構造方法最後也會走這一個構造方法)是有7個引數,分別是地址、正確響應的回撥、最大寬度、最大高度、縮放型別、顏色屬性、請求失敗的回撥,在此建構函式中還設定了它的優先順序(優先順序設定的很低)和它的重試策略,重試兩次,超時時間比預設的小了很多,這些都不是重點,重點在這個parseNetworkResponse方法(基本上所有的請求的重點都是這個方法),看他是如何解析響應資料。首先根據我們傳入的最大寬度和最大高度載入合適的圖片( 具體如何計算這裡,說白了就是小學算數,唯一要注意的是BitmapFactory.Options中的injustDecodeBounds引數,設定成true的時候是先讀取這個圖片的資訊,但不載入到記憶體,讀取到這些資訊之後我們就可以去算要怎麼縮放,計算好之後在真正去載入bitmap到記憶體,新手可能會不知道這個,這能很好的優化圖片載入,降低oom出現的風險 ),看著問題不大,但是如果非得較真的話其實是寫的不夠好的,主要的點就在bitmap的記憶體管理上,Bitmap的頻繁建立時很耗記憶體的(加入我們在一個listview上載入大量圖片),在Android 2.3.3 (API level 10) 以及更低版本上,使用recycle()方法可以使得程式更快的釋放記憶體,但是在Android 3.0 (API Level 11)開始,引進了BitmapFactory.Options.inBitmap欄位。 如果使用了這個設定欄位,decode方法會在載入Bitmap資料的時候去重用已經存在的Bitmap。這意味著Bitmap的記憶體是被重新利用的,這樣可以提升效能,並且減少了記憶體的分配與回收。具體如何做大家可以參考android官網:

管理Bitmap的記憶體
其中也要結合到軟引用的知識,大家可以支援一下我的另一篇文章: java 軟引用、弱引用、強引用、虛引用的解析

自定義請求上面講完了,主要大家要去寫的就是如何去解析response,其他也沒什麼,我只是講了一些大家可以去關注的點。其實對於怎麼寫自定義請求我屁都沒講。
這裡寫圖片描述

繼續圖片載入

看了上面ImageRequest的分析知道了有一個可以優化的點就是bitmap記憶體的複用,但是對於圖片的光就一個磁碟快取是不夠用的,還需要記憶體快取,但是單單自己實現一個自定義的request很難把記憶體快取的東西加上去,有什麼辦法呢!看下原始碼中的ImageLoader的實現,可以看到他已經不是一個請求了。
還是老規矩先看看他如何使用的

  1. 建立一個RequestQueue物件。
  2. 建立一個ImageLoader物件。
  3. 獲取一個ImageListener物件。
  4. 呼叫ImageLoader的get()方法載入網路上的圖片。

第一步我就不說了,從第二步開始

ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {
    @Override
    public void putBitmap(String url, Bitmap bitmap) {
    }

    @Override
    public Bitmap getBitmap(String url) {
        return null;
    }
});

第一個引數就是請求佇列,第二個引數就是快取,我們可以實現ImageCache介面實現自己的記憶體快取。比如:

public class BitmapCache implements ImageCache {

    private LruCache<String, Bitmap> mCache;

    public BitmapCache() {
        int maxSize = 10 * 1024 * 1024;
        mCache = new LruCache<String, Bitmap>(maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight();
            }
        };
    }

    @Override
    public Bitmap getBitmap(String url) {
        return mCache.get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        mCache.put(url, bitmap);
    }

}

程式碼摘自:Android Volley完全解析(二),使用Volley載入網路圖片這裡有比較詳細的Volley的使用方法的介紹。
將上面實現的BitmapCache 當做ImageLoader建構函式的第二個引數即可。
第三步:獲取一個Imagelistener物件,

ImageListener listener = ImageLoader.getImageListener(imageView,
        R.drawable.default_image, R.drawable.failed_image);

引數就不用介紹什麼意思了吧,大家一看就懂了。ImageListener繼承自Response的ErrorListener還自己又加了一個onResponse方法,它的意思就是載入有誤的時候呼叫onErrorResponse顯示錯誤圖片,載入成功就顯示載入的圖片(太簡單了我都懶得講)。
最後呼叫ImageLoader的get()方法,這才是重點(不像我們大學的時候考試,全時重點)。get方法主要就是五個引數:地址、上面實現的ImageListener 、最大寬度、最大高度、縮放型別(這個一般就使用預設的) 啊,接下來看看其中的實現流程:
1、判斷get方法是否執行在UI執行緒(因為要改變ui)。根據地址,最大寬高、縮放型別得到獲取快取需要的key
2、用上面得到的key去我們自己實現的快取中去拿對應的圖片資料,拿到資料就執行3,沒有拿到執行4
3、建立ImageContainer物件(有bitmap物件和地址),呼叫listener的onResponse方法顯示圖片,然後返回結束
4、同樣建立一個ImageContainer物件,但是和3中的不一樣,這個ImageContainer物件沒有bitmap,建立完之後也呼叫listener的onResponse方法,因為沒有bitmap所以顯示的是預設的圖片
5、從hashmap中拿取BatchedImageRequest,能拿到就執行6,拿不到就執行7
6、將ImageContainer物件新增到請求BatchedImageRequest中
7、建立一個ImageRequest,忘記了的看一看前面的內容,在成功得到請求圖片的時候講此bitmap儲存到快取中,並顯示圖片,否則顯示錯誤的圖片。
8、將此ImageRequest新增到請求佇列中

上面的就是基本的流程,可能會有問題的就是BatchedImageRequest和ImageContainer,其中ImageContainer裡記錄了bitmap、listener、key以及url,而BatchedImageRequest其中有一個LinkedList儲存的是ImageContainer物件,有什麼用呢,這裡的用處就是記錄相同請求,請求一次然後將請求結果迴圈讀取這個LinkedList進行分發,這麼講是不是清楚些
這裡寫圖片描述

這裡仔細想一下這裡有兩個地方是有問題的:

第一個問題:對於結果的分發,也就是在ImageRequest的onResponse和onErrorResponse中,大家看完我的上一篇文章之後應該能知道,預設的分發實現已經確保了上述兩個方法會在ui執行緒中執行,但是在這裡你跟蹤程式碼到最後進行分發的時候,又做了一次確保在ui執行緒分發的工作(雖然最後又一個不一樣的地方:做了延時,但我覺得沒什麼必要),也有可能有人會覺得這樣做沒有問題,確保不會有錯,但是就我看來這就是重複工作,還是避免這種重複工作。
第二個問題:假設我們有兩個相同的請求,第一個請求建立新增進了佇列並新增入儲存BatchedImageRequest的hashmap中,此時第二個相同的請求立馬進來,而第一個請求還沒執行到判斷是否取消請求的那句,於是第二個請求就加入到了那個LinkedList中返回,這個時候第一個請求還是沒有執行到判斷是否取消請求的那句,到這裡我們在取消第一個請求,會發生什麼呢!!!第二個請求永遠都執行不到了,因為它沒有加入到請求佇列中。雖然這種情況是很小几率的,但這依舊是個bug。

就上述所說的問題的修正

上面的而第二個問題其實是我想錯了,但是這裡還是留著比較好,畢竟也是自己走過的路,為什麼說沒有問題呢!主要看ImageLoader怎麼取消一個請求,就單單按我上面所說肯定是有問題的,但是它不是那麼取消的,首先ImageLoader沒有繼承自Request,如果要取消一個請求,需要使用的是ImageContainer裡的cancelRequest方法(get方法返回的就是一個ImageContainer物件,很方便我們取消)。

哪裡還可以優化

1、我們可以看到Cache的預設實現DiskBasedCache的實現,在超過限制的大小之後,他就去刪除檔案,這樣寫是對的,但是它的刪除策略有些隨意,很可能出現你剛放進去的,馬上就被刪除的情況。可以考慮使用lru或者lru結合過期什麼的。
2、(這個是我在其他地方看到原話照拿來的:Android的volley框架心得) Volley中DiskBasedCache初始化非常慢的問題。 initialize方法中,遍歷所有的快取檔案,讀取頭資訊存入map中,當快取檔案非常多的時候,google提供的初始化實現,耗費了10s以上的時間!!!不知道這是不是個bug。也就是說,每次newRequestQueue,都需要等待十秒的時間才能開始處理request。如果這樣的話,這個框架就沒法用了。 目前我也沒找到好的方法。有兩個優化方案。 (1) 有人在stackoverflow中提到過這個問題,然後他給出瞭解決方案。在initialize中,給FileOutputStream在套一層BufferedOutputStream。本人測試過,速度有十倍的提升。基本能控制初始化的時間在1-3s。 (2) 提前初始化。每一次newRequestQueue都要花費這麼長時間初始化,但是實際使用中,我們需要new那麼多RequestQueue嗎?看原始碼可以知道,每個RequestQueue實際啟動了5個迴圈執行緒來排程各種請求。其中四個用來處理網路請求,一個用來處理cache請求。對於一個app來說,這樣已經足夠,我們沒必要每個頁面都啟動一個RequestQueue。因此,我把RequestQueue設定為單例,在Application中初始化,這樣就可以避免initialize被反覆呼叫,同時也能在介面出來之前,預先準備好資料。
其他的優化還沒有發現,大家如果知道的請一定要告訴我!
這裡寫圖片描述

最後Volley原始碼分析大致就到這裡結束了,之後就不會再講解Volley的原始碼了,但還是會拿Volley和其他一些圖片請求框架進行比較,敬請期待!