本帖最後由 boredream 於 2014-5-27 09:07 編輯
ImageLoader和Volley圖片部分還包括其他大部分圖片框架,基本片處理都差不多,區別僅在於部分最佳化了,而最佳化方面UIL即Universal-Image-Loader架構做的最好,所以這部分章節算是溫習一片處理以及尋找下其他架構裡面一些不一樣的圖片處理方式(只關注圖片方面)
首先是ImageLoader https://github.com/novoda/ImageLoader 主要還是分析圖片載入的核心代碼部分,其他地方簡略介紹
單張圖片的縮放問題 核心方法如下~ int calculateScale(final int requiredSize, int widthTmp, int heightTmp) { int scale = 1; while (true) { if ((widthTmp / 2) < requiredSize || (heightTmp / 2) < requiredSize) { break; } widthTmp /= 2; heightTmp /= 2; scale *= 2; } return scale;}也是寫法換了一下,其實意義等同於UIL架構中的CROP類型時的縮放,也等同於官方推薦的基本處理(參見教程一)如下while (srcWidth / 2 >= targetWidth && srcHeight / 2 >= targetHeight) { // && srcWidth /= 2; srcHeight /= 2; scale *= 2;}一模一樣~!~!~!~! 兩個條件完全反過來,而一個是while繼續的條件,一個是break中斷的條件,所以綜合起來實際意義是完全一樣滴ImageLoader架構相當於我們教程一裡面的,只有對一種情況的解析,沒有UIL那種對不同縮放方式的區別處理 此外是色彩樣式修改架構原始碼全域搜尋了下關鍵字,也找了首頁文檔介紹,貌似沒有發現ImageLoader對色樣有設定,需要修改色樣的話需要自己實現了 緩衝池部分ImageLoader架構分三種緩衝池LruBitmapCache LRU演算法的強引用緩衝NoCache 無緩衝情況(其實就不能算緩衝池了,沒啥意義一般不會使用這個類)SoftMapCache 軟引用緩衝 支援的類型比較少,緩衝池只能算有兩種,單獨使用強引用和單獨使用軟引用這兩種,沒有二級緩衝的處理同樣也是提供三個類型,由使用者自行設定緩衝類型,設定方法為SettingsBuilder.withCacheManager(...) LruBitmapCache強引用緩衝的分析 架構裡面自訂了一個LruCache類,不是官方的LruCache類,但是實現邏輯都相似(基本算是一樣),我對比了下,就相當於官方的LruCache類+UIL架構強引用類的綜合體了~ 內部實現也是一個LinkedHashMap,關鍵方法名字是put和trimToSize(本來還想吐槽為啥和UIL架構名字一樣- -, 最後發現都是模仿官網LruCache類裡方法起的名字), 此外還有一個entryRemoved的方法,也是直接模仿官方LruCache寫的一方法,意思是超過強引用緩衝池閥值時,移除其中最老的對象,可以子類複寫此方法,對這個移除的對象做所需處理(比如將其移至軟引用緩衝中,自己實現一個二級緩衝)public final V put(K key, V value) { if (key == null || value == null) { throw new NullPointerException( "key == null || value == null" ); } V previous; synchronized ( this) { putCount++; size += safeSizeOf(key, value); previous = map.put(key, value); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved( false, key, previous, value); } trimToSize(maxSize); return previous;} 再簡單的嘮叨下吧putCount記錄添加數量的,這裡暫時用不上不介紹synchronized是為了保證多線程訪問時size計算包括putCount的計算不要出現混亂首先size += 將put的對象的大小加至當前緩衝池大小size值上map.put的傳回值是已有對於key時返回的被替換value值,如果非空則代表上一個被移除了,自然要-=減去其size值然後就是關鍵的entryRemoved方法了,方法內部是無內容的,只是相當於將移除的bitmap對象作為參數傳入方法中最後調用trimToSize方法檢測當前緩衝大小是否大於閥值,做對應處理 private void trimToSize(int maxSize) { while (true) { K key = null; V value = null; synchronized ( this) { if ( size < 0 || ( map.isEmpty() && size != 0)) { throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!" ); } if ( size <= maxSize) { break; } // Change if ( map.entrySet().iterator().hasNext()) { Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } } entryRemoved( true, key, value, null); }}和UIL架構也差不多(其實都是跟官方LruCache類裡對於方法改的,所以名字邏輯幾乎都沒區別)檢測size是否超過maxSize,是的話移除之~ 然後對於size處理下 entryRemoved方法 內部是空的,參數做個簡單介紹,主要是第一個boolean參數,可以直接參考LinkedHashMap的源碼裡相似方法 意思就是 true是為了移除對象保證空間才移除的對象,而false是平常put替換還有remove時移除的對象, 那麼如果想在這個架構裡實現二級緩衝,就可以自訂子類複寫Imageloader架構的LruCache類,然後複寫這個類, 在裡面判斷,如果是第一個參數為true,則將其儲存至一個軟應用/弱引用的二級緩衝池中
SoftMapCache軟引用緩衝 只有一個軟引用,無數量控制所以沒啥好分析的,就一個簡單的Map<String, SoftReference<Bitmap>> cache類型集合 然後提供put remove等基本方法
總的來說,最基本的都有,但是不夠完善,需要自行實現諸如二級緩衝等一些加強功能 現在幾乎都用UIL架構了,所以ImageLoader基本僅提供個小參考了,如果想研究源碼又覺得UIL比較複雜,倒是可以看看ImageLoader,在項目裡使用的話,還是推薦UIL,我~相信群眾~
---------------------------------------------------------------------------
Volley架構圖片載入部分 不同於之前兩個架構都是專註做圖片載入的,這個架構是綜合型的,且主要優勢在於網路資料非同步請求部分,而圖片載入部分其實功能不是很完整,僅提供最基本的處理,下面分析一下
首先還是單張圖片的處理 同樣,全域搜一下關鍵字inSampleSize定位到ImageRequest類裡的一個方法 /*** The real guts of parseNetworkResponse. Broken out for readability.*/private Response<Bitmap> doParse(NetworkResponse response) { byte[] data = response. data; BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); Bitmap bitmap = null; if (mMaxWidth == 0 && mMaxHeight == 0) { decodeOptions. inPreferredConfig = mDecodeConfig; bitmap = BitmapFactory. decodeByteArray(data, 0, data.length, decodeOptions); } else { // If we have to resize this image, first get the natural bounds. decodeOptions. inJustDecodeBounds = true; BitmapFactory. decodeByteArray(data, 0, data.length, decodeOptions); int actualWidth = decodeOptions. outWidth; int actualHeight = decodeOptions. outHeight; // Then compute the dimensions we would ideally like to decode to. int desiredWidth = getResizedDimension(mMaxWidth , mMaxHeight , actualWidth, actualHeight); int desiredHeight = getResizedDimension(mMaxHeight , mMaxWidth , actualHeight, actualWidth); // Decode to the nearest power of two scaling factor. decodeOptions. inJustDecodeBounds = false; // TODO (ficus): Do we need this or is it okay since API 8 doesn‘t support it? // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED; decodeOptions. inSampleSize = findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight); Bitmap tempBitmap = BitmapFactory. decodeByteArray(data, 0, data.length, decodeOptions); // If necessary, scale down to the maximal acceptable size. if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) { bitmap = Bitmap. createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true); tempBitmap.recycle(); } else { bitmap = tempBitmap; } } if (bitmap == null) { return Response.error(new ParseError(response)); } else { return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response)); }}兩個地方是關鍵,一個是getResizedDimension一個是findBestSampleSize,分別介紹下
先看後一個findBestSampleSize,我們比較熟的邏輯方法如下 static int findBestSampleSize ( int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) { double wr = (double) actualWidth / desiredWidth; double hr = (double) actualHeight / desiredHeight; double ratio = Math.min(wr, hr); float n = 1.0f; while ((n * 2) <= ratio) { n *= 2; } return (int) n; }又一種演算法~但實際意義還是和教程一裡的一樣~數學邏輯好的應該一下就能看出來,看不出來的...做個demo對比下幾個方法對不同值的計算結果,懶得測試的,那就信我的話吧,基本是木有區別的具體計算效率的區別我水平有限就不研究了,不過這種寫法看著貌似顯得流弊點?自己需要寫圖片壓縮值計算的時候可以用用~ 然後是另一個方法的介紹getResizedDimension,以前沒見過,有點參考價值~ 擷取調整後的大小尺寸,也就是擷取所需壓縮圖片大小值的一個計算方法,處理方法挺特別的,可以略過,其實不同通過這個方法進行"調整",直接使用自訂的maxWidth maxHeight限定寬高值就行了,以下這部分是對此方法的簡單分析,精力有限的可以跳過~
-----------------------------------------------------------------------------------
本來想偷懶網上搜下這方法的說明,可惜最多查到一句"按一定規則計算出...", 算了 我還是自己跑項目邊研究邊死扣吧~其實這段方法學習參考價值更大一點,實際上設定所需壓縮寬高值以後,圖片就會按照之前那種規則去擷取一個適當的值了,即不採用本方法壓縮效果也是可以實現的 /** * Scales one side of a rectangle to fit aspect ratio. * * @param maxPrimary Maximum size of the primary dimension (i.e. width for * max width), or zero to maintain aspect ratio with secondary * dimension * @param maxSecondary Maximum size of the secondary dimension, or zero to * maintain aspect ratio with primary dimension * @param actualPrimary Actual size of the primary dimension * @param actualSecondary Actual size of the secondary dimension */ private static int getResizedDimension (int maxPrimary, int maxSecondary, intactualPrimary, int actualSecondary) { // If no dominant value at all, just return the actual. if (maxPrimary == 0 && maxSecondary == 0) { return actualPrimary; } // If primary is unspecified, scale primary to match secondary‘s scaling ratio. if (maxPrimary == 0) { double ratio = ( double) maxSecondary / ( double) actualSecondary; return ( int) (actualPrimary * ratio); } if (maxSecondary == 0) { return maxPrimary; } double ratio = ( double) actualSecondary / ( double) actualPrimary; int resized = maxPrimary; if (resized * ratio > maxSecondary) { resized = ( int) (maxSecondary / ratio); } return resized; }方法傳入4個資料,即兩組 一組是max,即限定值,分成兩個 主值和次值, 不是寬高的意思,主值有可能是寬則次值是高, 也有可能是高為主值,寬為次值 另一組則是actual實際值,即圖片的原寬高數值, 也分成主和次, max主為限定高度,次為限定寬度時, 實際值也對於主為高,次為寬,反之亦然 - - 意義不明?我們接著看
使用方法是調用兩次,主為高次為寬時,計算的結果是調整後所需高度值,也就是計算為主的那個所需值 同理,主為寬時,計算結果是調整後的所需寬度值 - - 意義還是不明?我們再接著看
舉個實際例子吧 如果設定了限定的寬高值,比如maxWidth = 180 maxHeight = 200, 原圖我這邊是載入的一張圖片寬*高是 720*617的圖 分別調用兩次方法擷取所需值 int desiredWidth = getResizedDimension(180, 200, 720, 617); //擷取調整後的所需寬度值 int desiredHeight = getResizedDimension(200,180, 617,720); //擷取調整後的所需高度值計算結果desiredWidth = 180;desiredHeight = 154; 直觀上理解就是,將所需寬高值調整成了與原圖寬高一樣的比例(所需寬高調整後比例180/154≈1.17 原圖比例720/617≈1.17) 再次測試,同一張圖片,maxWidth和maxHeight都設成200,計算後結果是desiredWidth = 200;desiredHeight = 171;寬高比也和原圖比例相同(200/171≈1.17) 即,在限定寬高都設定過的情況下(都不為0),計算原圖比例然後將限定寬高大小調整為與原圖一致的比例,且調整後的寬高值都小於等於原限定值寬高任一方為0或者都為0的情況這裡就不分析了,不是太重要,是對限定值為0即未設定數值時進行的一個對應處理 之前UIL架構中針對兩種不同縮放類型,將壓縮比例值計算區分成了兩種,一個是||串連條件,一個是&&串連條件||的處理結果是保證壓縮後圖片任意一條邊大於限定值對應邊即可&&的處理結果是保證壓縮後圖片寬高兩條邊都要大於限定值對應邊 &&的處理與官方提供的例子一致,也是教程一中介紹的計算方法那volley的這種演算法,簡單實驗了幾個資料,我發現結果和UIL中FIT_INSIDE情況即||串連條件計算的結果差不多,我也擼了簡單demo對比了更多組不同資料下兩者的結果,也都是一致的~邏輯上理解,如果限定值和原圖比例不同,那麼處理後的限定值其中一個邊會減少~那以這個調整後的限定值壓縮圖片的話,最終結果就有可能出現: 最後擷取的壓縮圖片樣式一條邊小於調整前我們設定的限定值對應邊最終造成了只有一條邊滿足大於等於限定值的情況,即與UIL中FIT_INSIDE情況相同 總結,官方的方法是保證無論什麼縮放顯示類型,都能保證壓縮後圖片寬高值大於等於限定值UIL比較好,不同情況都有考慮,比例值計算的比較精確Volley架構則是另一種了,即預設情況下能夠保證圖片清晰度(像素密度能達到目標),但是CROP等縮放類型下尤其是長寬比較大的情況時,則壓縮後圖片無法保證清晰度(由於此情況較少,所以大部分情況下的壓縮品質還是有保證的)注:可能有不太準確的地方,我正在整理 1.教程一官方方法 2.UIL架構 3.Volley架構圖片處理 三者的具體資料對比,因為資料量比較大且雜,所以之後如~果~能整理順利且有時間的話,就單開一章對架構做個簡單比較~ --------------------------------------------------------------------------- 色彩樣式部分 架構源碼裡是將圖片色彩樣式設為RGB_565,也是直接寫死的 多張圖片緩衝池一個木有- - 只提供了一個緩衝池類型介面ImageCache,需要自訂類實現它網上主流的做法,包括demo都是自訂一個類繼承LruCache然後實現Volley架構中對應介面類寫法也很簡單,教程二熟悉的話估計都會寫,類代碼如下public class BitmapLruCache extends LruCache<String, Bitmap> implements ImageCache { public BitmapLruCache( int maxSize) { super(maxSize); } @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override public Bitmap getBitmap(String url) { return get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { put(url, bitmap); }}同樣,你也可以利用LinkedHashMap實現,還可以添加上二級緩衝的結構都可以 ---------------------------------------------------------------------------
簡單過了一遍ImageLoader架構和Volley架構圖片部分,再次從側面驗證了我們教程中方法滴靠譜性~ 也簡單對這倆架構有了一些瞭解, 如果有興趣可以自己找幾個較為有名的架構自己看一下源碼,估計差別也只在最佳化部分,核心邏輯都是相同的 Volley架構相對其他架構而言,功能其實算是很弱了,只有最最基本的處理邏輯,甚至緩衝池都需要自訂類實現,但這也是一個優點,因為Volley是一個綜合型架構,已經提供了一個很好的地基,網路資料部分無需我們考慮太多,如果想做一個屬於自己的架構,又不想完全一點點自己寫,那Volley就是你最好的選擇了,我們可以根據需要進行架構的二次開發~這也是我專門拿Volley出來介紹的原因,之後一章會對幾個架構的不同圖片處理效果做個簡單對比,再之後一章就是Volley架構圖片緩衝的二次開發了,我也是第一次弄,肯定有不足的地方,到時候大家一起探討~ |