Android Bitmap 全面解析

整理自帖子:http://www.eoeandroid.com/thread-331669-1-1.htmlhtml


(一) 加載大尺寸圖片

壓縮緣由:

1.imageview大小若是是200*300那麼加載個2000*3000的圖片到內存中顯然是浪費可恥滴行爲;
2.最重要的是圖片過大時直接加載原圖會形成OOM異常(out of memory內存溢出)


因此通常對於大圖咱們須要進行下壓縮處理
權威處理方法參考 安卓開發者中心的大圖片處理教程
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

主要處理思路是:
1.獲取圖片的像素寬高(不加載圖片至內存中,因此不會佔用資源)
2.計算須要壓縮的比例
3.按將圖片用計算出的比例壓縮,並加載至內存中使用


官網大圖片加載教程(上面網址裏的)對應代碼就是:
/**
* 獲取壓縮後的圖片
* @param res
* @param resId
* @param reqWidth            所需圖片壓縮尺寸最小寬度
* @param reqHeight           所需圖片壓縮尺寸最小高度
* @return
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {
   
    // 首先不加載圖片,僅獲取圖片尺寸
    final BitmapFactory.Options options = new BitmapFactory.Options();
    // 當inJustDecodeBounds設爲true時,不會加載圖片僅獲取圖片尺寸信息
    options.inJustDecodeBounds = true;
    // 此時僅會將圖片信息會保存至options對象內,decode方法不會返回bitmap對象
    BitmapFactory.decodeResource(res, resId, options);

    // 計算壓縮比例,如inSampleSize=4時,圖片會壓縮成原圖的1/4
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // 當inJustDecodeBounds設爲false時,BitmapFactory.decode...就會返回圖片對象了
    options. inJustDecodeBounds = false;
    // 利用計算的比例值獲取壓縮後的圖片對象
    return BitmapFactory.decodeResource(res, resId, options);
}
代碼詳解:
核心方法是BitmapFactory.decode...(...., options)
...的意思是此外還有一系列的decodeFile/decodeStream等等方法,都是利用options靈活解析獲取圖片,只不過解析圖片的來源不一樣罷了,好比網絡圖片獲取,通常就是解析字節流信息而後decode獲取圖片實例

Options是圖片配置信息,參數詳細介紹下:

inJustDecodeBounds 是否只解析邊界
     設爲true時去decode獲取圖片,只會加載像素寬高信息
     設爲false時decode則會徹底加載圖片

inSampleSize 壓縮比例
     好比原圖200*300,若是值是2時會壓縮成100*150; 是4則圖片壓縮成50*75
     最好是2的冪數,好比2 4 8 16 .....

outHeight  圖片原高度
outWidth  圖片原寬度

其餘參數自行研究,這裏暫時只用到這幾個


decodeSampledBitmapFromResource方法內的三段代碼對應上面的三步流程。難點在於中間那步,壓縮比例的計算,官網一樣提供了個calculateInSampleSize方法,其中reqWidth和reqHeight是所需圖片限定最小寬高值
/**
* 計算壓縮比例值
* @param options       解析圖片的配置信息
* @param reqWidth            所需圖片壓縮尺寸最小寬度
* @param reqHeight           所需圖片壓縮尺寸最小高度
* @return
*/
public static int calculateInSampleSize(BitmapFactory.Options options,
             int reqWidth, int reqHeight) {
       // 保存圖片原寬高值
       final int height = options. outHeight;
       final int width = options. outWidth;
       // 初始化壓縮比例爲1
       int inSampleSize = 1;

       // 當圖片寬高值任何一個大於所需壓縮圖片寬高值時,進入循環計算系統
       if (height > reqHeight || width > reqWidth) {

             final int halfHeight = height / 2;
             final int halfWidth = width / 2;

             // 壓縮比例值每次循環兩倍增長,
             // 直到原圖寬高值的一半除以壓縮值後都~大於所需寬高值爲止
             while ((halfHeight / inSampleSize) >= reqHeight
                        && (halfWidth / inSampleSize) >= reqWidth) {
                  inSampleSize *= 2;
            }
      }

       return inSampleSize;
}
利用此方法獲取到所需壓縮比例值,最終獲取到壓縮後的圖片~

官網的這個方法是: 將圖片一半一半的壓縮,直到壓縮成成 大於所需寬高數的那個最低值。  大於~不是大於等於 ,因此就會出現我上面那種狀況,我以爲方法不是太好= = 能知足壓縮的需求,可是壓縮的比例不夠準確, 因此最好改爲大於等於,以下(我的意見,僅供參考,在實際壓縮中不多遇到恰巧等於的這個狀況,因此>和>=差異也不大額~看我這扯扯淡就當對計算比例的邏輯加深個理解吧)

----------------------------------------------------------


以上,圖片的像素大小已經作了縮放。可是圖片的大小除了和像素有關。還和色彩樣式有關。 不一樣的樣式決定了圖片單個像素佔的字節數
好比。圖片默認的色彩樣式爲ARGB_8888。每一個像素佔4byte(字節)大小
參考資料:http://developer.android.com/reference/android/graphics/Bitmap.Config.html

能夠看到一共有四種色彩樣式
ALPHA_8      每一個像素只要1字節~惋惜只能表明透明度,沒有顏色屬性
ARGB_4444    每一個像素要2字節~帶透明度的顏色~惋惜官方不推薦使用了
ARGB_8888  每一個像素要4字節~帶透明度的顏色, 默認色樣
RGB_565    每一個像素要2字節~不帶透明度的顏色
默認爲ARGB_8888,若是想喪心病狂的繼續減小圖片所佔大小。不須要透明度參數的話, 那就能夠把色彩樣式設爲RGB_565

設置方法是在BitmapFactory.decode..獲取圖片事例時, 修改配置參數的inPreferredConfig 參數
opts. inPreferredConfig = Bitmap.Config. RGB_565 ;


----------------------------------------------------------

想親自擼一擼試一試壓縮圖片了吧?
要注意點問題,若是用res包下圖片測試的話,你會發現有圖片尺寸有點混亂。那是由於在drawable-*dpi文件夾中的圖片會根據對應的屏幕密度值不一樣自動進行必定的縮放,好比放在drawable-hdpi裏的圖片,直接不通過壓縮BitmapFactor.decode..出來,會發現bitmap的寬高值是原圖的2/3。測試的時候圖片記得放在drawable包下(沒有的話本身res下新建一個),不然你會被奇怪的寬高值弄凌亂的。具體變化緣由參考源代碼處理,或者網上搜搜看。

還有就是BitmapFactory.decodeStream方法會偶爾解析圖片失敗(好像是安卓低版本的一個bug)。
此時推薦作法是將流轉換爲字節流處理,而後利用decodeByteArray方法獲取圖片



(二)加載多張圖片的緩存處理

通常少許圖片是不多出現OOM異常的,除非單張圖片過大。那麼就能夠用第一節裏面的方法了。
一般應用場景是listview列表加載多張圖片,爲了提升效率通常要緩存一部分圖片,這樣方便再次查看時能快速顯示,不用從新下載圖片。可是手機內存是頗有限的,當緩存的圖片愈來愈多,即便單張圖片不是很大,不過數量太多時仍然會出現OOM的狀況了。
前端

本篇則是討論多張圖片的處理問題

-----------------------------------------------------------------------

圖片緩存的通常處理

1.創建一個圖片緩存池,用於存放圖片對應的bitmap對象。
2.在顯示的時候,好比listview對應適配器的getView方法裏進行加載圖片的工做, 先從緩存池經過url的key值取,若是取到圖片了直接顯示,若是獲取不到再創建異步線程去下載圖片(下載好後同時保存至圖片緩存池並顯示)

可是緩存池不能無限大啊,否則就會異常了,因此一般咱們要對緩存池進行必定控制。
須要有兩個特性: 
     1.總大小有個限制,否則裏面存放無限多的圖片時會內存溢出OOM異常
     2.當大小達到上限後,再添加圖片時,須要線程池可以智能化的回收移除池內一部分圖片,這樣才能保證新圖片的顯示保存


異步線程下載圖片神馬的簡單,網上異步下載任務的代碼一大堆,下載之後流數據直接decode成bitmap圖片便可。
難點在與這個圖片緩存池的設計,如今網上的實現主要有兩種
1.軟引用/弱引用
2.LruCache

-----------------------------------------------------------------------

拓展: java中4種引用分類

官方資料鏈接:http://developer.android.com/reference/java/lang/ref/Reference.html
強引用
     日常使用的基本都是強引用,除非主動釋放(圖片的回收,或者==null賦值爲空等),不然會一直保存對象到內存溢出爲止~
軟引用    SoftReference
     在系統內存不夠時,會自動釋放部分軟引用所指對象~
弱引用    WeakReference
     系統偶爾回收掃描時發現弱引用則釋放對象,即和內存夠不夠的狀況無關,徹底看心情~
虛引用
     不用瞭解,其實我也不熟悉

框架基本都比較愛用這個軟應用保存圖片做爲緩存池,這樣在圖片過多不足時,就會自動回收部分圖片,防止OOM。可是有缺點,沒法控制內存不足時會回收哪些圖片,若是我只想回收一些不經常使用的,不要回收經常使用的圖片呢?


因而引入了二級緩存的邏輯,即設置兩個緩存池,一個強引用,一個軟引用, 強引用保存經常使用圖片,軟應用保存其餘圖片。強引用由於不會自動釋放對象,因此大小要進行必定限定,不然圖片過多會異常, 好比控制裏面只存放10張圖片,而後每次往裏面添加圖片的時候,檢查若是數量超過10張這個閥值,臨界點值時,就移除強引用裏面最不經常使用的那個圖片,並將其保存至軟應用緩存池中。


整個緩存既做爲一個總體(一級二級緩存都是內存緩存~每次顯示圖片前都要檢查整個緩存池中有沒有圖片)。又有必定的區分(只回收二級緩存軟引用中圖片,不回收一級緩存中強引用的圖片)

 

代碼實現
軟應用緩存池類型做爲二級緩存: 
java

<span style="font-family:Microsoft YaHei;">HashMap<String, SoftReference<Bitmap>> mSecondLevelCache = new HashMap<String, SoftReference<Bitmap>>();</span>


強引用做爲一級緩存,爲了實現刪除最不經常使用對象,能夠用LinkedHashMap<String,Bitmap>類。LinkedHashMap對象能夠複寫一個removeEldestEntry,這個方法就是用來處理刪除最不經常使用對象邏輯的,按照以前的設計就能夠這麼寫:android

<span style="font-family:Microsoft YaHei;">    final int MAX_CAPACITY = 10; // 一級緩存閾值

    // 第一個參數是初始化大小
    // 第二個參數0.75是加載因子爲經驗值
    // 第三個參數true則表示按照最近訪問量的高低排序,false則表示按照插入順序排序
    HashMap<String, Bitmap> mFirstLevelCache = new LinkedHashMap<String, Bitmap>(
            MAX_CAPACITY / 2, 0.75f, true) {
        // eldest 最老的對象,即移除的最不經常使用圖片對象
        // 返回值 true即移除該對象,false則是不移除
        protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
            if (size() > MAX_CAPACITY) {// 當緩存池總大小超過閾值的時候,將老的值從一級緩存搬到二級緩存
                mSecondLevelCache.put(eldest.getKey(),
                        new SoftReference<Bitmap>(eldest.getValue()));
                return true;
            }
            return false;
        }
    };</span>

每次圖片顯示時即便用時,若是存在與緩存中,則先將對象從緩存中刪除,而後從新添加到一級緩存中的最前端。
會有三種狀況
1.若是圖片是從一級緩存中取出來的,則至關於把對象移到了一級緩存池的最前端(至關於最近使用的一張圖片)
2.若是圖片是從二級緩存中取出來的,則會存到一級緩存池最前端並檢測,若是超過閥值,則將最不經常使用的一個對象移動到二級緩存中
3.若是緩存中沒有,那就網上下載圖片,下載好之後保存至一級緩存中,一樣再進行檢測是否要移除一個對象至二級緩存中

-----------------------------------------------------------------------

git

Disk緩存

能夠簡單的理解爲將圖片緩存到sd卡中。

因爲內存緩存在程序關閉第二次進入時就清空了,對於一個十分經常使用的圖片好比頭像一類的,咱們但願不要每次進入應用都從新下載一遍,那就要用到disk緩存了,直接圖片存到了本地,打開應用時直接獲取顯示。

網上獲取圖片的大部分邏輯順序是:
內存緩存中獲取顯示(強引用緩存池->弱引用緩存池)  -> 內存中找不到時從sd卡緩存中獲取顯示 -> 緩存中都沒有再創建異步線程下載圖片,下載完成後保存至緩存中。

按照獲取圖片獲取效率的速度,由快到慢的依次嘗試幾個方法

以文件的形式緩存到SD卡中,優勢是SD卡容量較大,因此能夠緩存不少圖片,且屢次打開應用均可以使用緩存,缺點是文件讀寫操做會耗費一點時間。雖然速度沒有從內存緩存中獲取速度快,可是確定比從新下載一張圖片的速度快,並且還不用每次都下載圖片浪費流量。因此使用優先級就介於內存緩存和下載圖片之間了。

注意:
sd卡緩存通常要提早進行一下是否裝載sd卡的檢測, 還要檢測sd卡剩餘容量是否夠用的狀況,程序裏也要添加註明相應的權限

-----------------------------------------------------------------------

使用LruCache處理圖片緩存


以上基本徹底掌握了,每一張圖最好再進行一下第一節裏面介紹的單張縮放處理,那基本整個圖片緩存技術就差很少了。但隨着android sdk的更新,新版本其實提供了更好的解決方案,下面介紹一下

先看老版本用的軟引用官方文檔
http://developer.android.com/reference/java/lang/ref/SoftReference.html


摘取段對軟引用的介紹
github

Avoid Soft References for Caching

In practice, soft references areinefficient for caching. The runtime doesn't have enough information on whichreferences to clear and which to keep. Most fatally, it doesn't knowwhat to do when given the choice between clearing a soft reference and growingthe heap.
The lack of information on the value to your application of each referencelimits the usefulness of soft references. References that are cleared too earlycause unnecessary work; those that are cleared too late waste memory.

Most applications should use an android.util.LruCache instead of soft references.LruCache has an effective eviction policy and lets the user tune how muchmemory is allotted.



簡單翻譯一下
咱們要避免用軟引用去處理緩存 。

在實踐中,軟引用在緩存的處理上是沒有效率的。運行時沒有足夠的信息用於判斷哪些引用要清理回收還有哪些要保存。最致命的,當同時面對清理軟引用和增長堆內存兩種選擇時它不知道作什麼。對於你應用的每個引用都缺少有價值的信息,這一點限制了軟引用讓它的可用性十分有限。過早清理回收的引用致使了無必要的工做; 而那些過晚清理掉的引用又浪費了內存。

大多數應用程序應該使用一個android.util.LruCache代替軟引用。LruCache有一個有效的回收機制,讓用戶可以調整有多少內存分配。

簡而言之,直接使用軟引用緩存的話效果不咋滴,推薦使用LruCache。level12之後開始引入的,爲了兼容更早版本,android-support-v4包內也添加了一個LruCache類,因此在12版本以上用的話發現有兩個包內都有這個類,其實都是同樣的。

那麼這個類是作啥的呢~這裏是官方文檔
http://developer.android.com/reference/android/util/LruCache.html

LRU的意思是LeastRecently Used 即近期最少使用算法。眼熟吧,其實以前的二級緩存中的那個強引用LinkedHashMap的處理邏輯其實就是一個LRU算法,而咱們查看LruCache這個類的源代碼時發現裏面其實也有一個LinkedHashMap,大概掃了眼,邏輯和咱們以前本身寫的差很少。核心功能基本都是: 當添加進去新數據且達到限制的閥值時,則移除一個最少使用的數據。

根據這個新的類作圖片加載的話,網上大部分的作法仍是二級緩存處理。只不過將LinkedHashMap+軟引用,替換成了LruCache+軟引用。都是二級緩存,強引用+軟引用的結構。由於LruCache和LinkedHashMap都是差很少的處理邏輯,沒有移除軟引用的使用,而是將二者結合了起來。

根據官網的介紹來看其實軟引用效果不大,二級緩存的處理的話,雖然能提升一點效果,可是會浪費對內存的消耗,因此要不要加個軟引用的二級緩存,具體選擇就看本身理解和實際應用場景了吧。

LruCache我理解是犧牲一小部分效率,換取部份內存。我我的也是傾向於只使用LruCache的實現不用軟引用了,也比較簡單。

-----------------------------------------------------------------------

LruCache的具體用法


以前對LinkedHashMap有了必定了解了,其實LruCache也差很少,相似於removeEldestEntry方法的回收邏輯,在這個類裏面已經處理好了,通常咱們只須要處理對閥值的控制就好了。

閥值控制的核心方法是sizeOf()方法, 該方法的意思是返回每個value對象的大小size,默認返回的是1。即當maxSize(經過構造方法傳入)設爲10的時候就至關於限制緩存池只保留10個對象了,和上面LinkedHashMap的例子一個意思。

可是因爲圖片的大小不一,通常限定全部圖片的總大小更加合適,那咱們就能夠對這個sizeOf方法進行復寫
@Override
protected int sizeOf(String key, Bitmapvalue) {
     return value.getRowBytes() * value.getHeight();
}

這樣的話,至關於緩存池裏每個對象的大小都是計算它的字節數,則在新建LruCache的時候傳入一個總size值就好了,通常傳入應用可用內存的1/8大小

-----------------------------------------------------------------------

本篇是討論對於圖片數量的控制問題, 再結合第一節中的方法對每一張圖片進行相應處理~OOM的狀況基本就能夠避免了~


-----------------------------------------------------------------------

(三)開源圖片框架分析1-UIL

主要介紹這三個框架,都挺有名的,其餘的框架估計也差很少了

Android-Universal-Image-Loader
https://github.com/nostra13/Android-Universal-Image-Loader

ImageLoader
https://github.com/novoda/ImageLoader

Volley(綜合框架,包含圖片部分)
https://github.com/mcxiaoke/android-volley


扯淡時間,能夠跳過這段。這些開源框架的源碼仍是挺複雜的,本人技術有限,有部分分析不對的地方或者不足的地方但願你們一塊兒討論,因爲有大量的源代碼分析,因此形成內容較多且比較雜亂,重點部分我會用紅字或者加粗字體標出來,若是沒有耐心所有看完的話能夠挑部分重點看,能夠跳過的部分我也會有說明,你們能夠選擇性閱讀。其實框架的實現和咱們教程(一)(二)節的也差很少,只不過在此基礎上進行了一些優化,核心部分是幾乎沒有區別的。

因爲篇幅有限,暫時只介紹第一個框架(其餘其實也都差很少),另外兩個在後續教程中詳細介紹

---------------------------------------------------------------------


首先介紹universal-image-loader(如下簡稱UIL),是github社區上star最多的一個項目,能夠理解爲點贊最多滴,應該是最有名的一個。國內不少知名軟件都用它包括淘寶京東聚划算等等。框架其實都差很少,這裏詳細介紹下使用者最多的一個,以後再分析其餘框架時就簡單說明一些特性了,重複類似部分再也不贅述了。

使用比較簡單,這個框架的github主頁上也有快速使用的步驟。基本上就是在Application類裏的onCreate方法(整個程序開始時運行一次)中進行一下簡單的基本配置。能夠根據須要自行進行設定,懶得設定的話框架也提供了一個默認的配置,調用一個方法便可
基本上是配置一些相似於:緩存類型啊,緩存上限值啊,加載圖片的線程池數量啊等等。

此外在頁面內顯示的時候還要設置一個顯示配置。這個配置不一樣於基本配置,一個項目裏能夠根據須要建立多個配置對象使用,
這個配置就比較具體了,能夠設置是否使用disk緩存(存到sd卡里通常),加載圖片失敗時顯示的圖片,默認圖片,圖片的色彩樣式等

配置好之後,就是簡單的使用了,建立一個圖片加載對象,而後一行代碼搞定顯示圖片功能。參數通常是入你須要顯示的圖片url和imageview對象。大部分框架其實都這一個尿性,配置稍微麻煩點,可是使用時通常只須要一行,顯示方法通常會提供多個重載方法,支持不一樣須要。

因爲不是框架使用教程,因此下面結合以前兩章的內容着重分析下框架對於單張圖片的壓縮處理,和多圖緩存池的處理
---------------------------------------------------------------------

面試

單張圖片的壓縮

(業界良心的小技巧: 框架確定也是基於android sdk的, 因此獲取圖片縮放實例的話,option的inSampleSize參數是確定要使用的, 咱們直接crtl+h打開搜索頁面,選擇file search, 而後file name patterns選擇*.java,即搜索全部java文件,最後在containing text上輸入想搜索的內容,這裏咱們要搜inSampleSize,搜索結果裏隨便掃一掃,發現BaseImageDecoder裏面有個靠譜的方法以下)

protected Options prepareDecodingOptions(ImageSize imageSize,
        ImageDecodingInfo decodingInfo) {
    ImageScaleType scaleType = decodingInfo.getImageScaleType();
    int scale;
    if (scaleType == ImageScaleType.NONE) {
        scale = ImageSizeUtils.computeMinImageSampleSize(imageSize);
    } else {
        ImageSize targetSize = decodingInfo.getTargetSize();
        boolean powerOf2 = scaleType == ImageScaleType.IN_SAMPLE_POWER_OF_2;
        scale = ImageSizeUtils.computeImageSampleSize(imageSize,
                targetSize, decodingInfo.getViewScaleType(), powerOf2);
    }
    if (scale > 1 && loggingEnabled) {
        L.d(LOG_SUBSAMPLE_IMAGE, imageSize, imageSize.scaleDown(scale),
                scale, decodingInfo.getImageKey());
    }

    Options decodingOptions = decodingInfo.getDecodingOptions();
    decodingOptions.inSampleSize = scale;
    return decodingOptions;
}

簡單掃一眼,ImageSize,ImageDecodingInfo神馬的明顯是自定義的一個類,不要管,咱們先挑重點部分看算法

Options decodingOptions =decodingInfo.getDecodingOptions();
decodingOptions.inSampleSize= scale;


方法最後兩行能夠看出來ImageDecodingInfo類裏面保存了一個option對象,經過一個方法對其中的inSampleSize進行了設置。

ImageScaleType.NONE 什麼意思,掃了眼註釋,是圖片無壓縮。那咱們看else裏面的須要壓縮的computeImageSampleSize方法。方法是具體如何處理的呢?咱們再繼續跟蹤computeImageSampleSize方法。設計模式


(業界良心小技巧:按着ctrl不鬆左鍵點擊方法或者變量或者類,就能夠自動跳轉到對應的地方了)

方法的代碼以下緩存

   /**
     * Computes sample size for downscaling image size (<b> srcSize</b> ) to
     * view size (<b>targetSize</b> ). This sample size is used during
     * {@linkplain BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options)
     * decoding image} to bitmap.<br />
     * <br />
     * <b>Examples: </b><br />
     * <p/>
     * 
     * <pre>
     * srcSize(100x100), targetSize(10x10), powerOf2Scale = true -> sampleSize = 8
     * srcSize(100x100), targetSize(10x10), powerOf2Scale = false -> sampleSize = 10
     * 
     * srcSize(100x100), targetSize(20x40), viewScaleType = FIT_INSIDE -> sampleSize = 5
     * srcSize(100x100), targetSize(20x40), viewScaleType = CROP -> sampleSize = 2
     * </pre>
     * <p/>
     * <br />
     * The sample size is the number of pixels in either dimension that
     * correspond to a single pixel in the decoded bitmap. For example,
     * inSampleSize == 4 returns an image that is 1/4 the width/height of the
     * original, and 1/16 the number of pixels. Any value <= 1 is treated the
     * same as 1.
     * 
     * @param srcSize
     *            Original (image) size
     * @param targetSize
     *            Target (view) size
     * @param viewScaleType
     *            {@linkplain ViewScaleType Scale type} for placing image in
     *            view
     * @param powerOf2Scale
     *            <i>true </i> - if sample size be a power of 2 (1, 2, 4, 8,
     *            ...)
     * @return Computed sample size
     */
    public static int computeImageSampleSize(ImageSize srcSize,
            ImageSize targetSize, ViewScaleType viewScaleType,
            boolean powerOf2Scale) {
        int srcWidth = srcSize.getWidth();
        int srcHeight = srcSize.getHeight();
        int targetWidth = targetSize.getWidth();
        int targetHeight = targetSize.getHeight();

        int scale = 1;

        int widthScale = srcWidth / targetWidth;
        int heightScale = srcHeight / targetHeight;

        switch (viewScaleType) {
        case FIT_INSIDE:
            if (powerOf2Scale) {
                while (srcWidth / 2 >= targetWidth
                        || srcHeight / 2 >= targetHeight) { // ||
                    srcWidth /= 2;
                    srcHeight /= 2;
                    scale *= 2;
                }
            } else {
                scale = Math.max(widthScale, heightScale); // max
            }
            break;
        case CROP:
            if (powerOf2Scale) {
                while (srcWidth / 2 >= targetWidth
                        && srcHeight / 2 >= targetHeight) { // &&
                    srcWidth /= 2;
                    srcHeight /= 2;
                    scale *= 2;
                }
            } else {
                scale = Math.min(widthScale, heightScale); // min
            }
            break;
        }

        if (scale < 1) {
            scale = 1;
        }

        return scale;
    }

我連註釋一塊兒複製過來了,裏面對參數有比較詳細的介紹,還有對應的例子~很良心的註釋啊。


能夠看到核心計算方法,其實和咱們教程(一)裏面是同樣的(區別在於這裏有兩個狀況的處理,一個是||一個是&&,後面會介紹緣由),對源圖的寬高和目標圖片的寬高進行一些判斷,知足時一直循環下去進行處理,直到取得一個合適的比例值爲止,最終保證
使壓縮後顯示的圖片像素密度是大於等於設定的像素密度的那個最低值。


(這裏換了個更合適的說法,由於框架對圖片不一樣scaleType形成的不一樣顯示效果進行的區別處理,使得壓縮比例值計算的更加精確,其實不用這個優化處理已經可以知足圖片縮放顯示需求了,這裏是爲了作到更好,固然也會更麻煩了,因此做爲咱們學習者來講要自行取捨要研究到哪一個深度更合適,後面有單獨一部分進行介紹,能夠選擇性看)

框架進行了優化,添加了一個powerOf2Scale的參數和viewScaleType的參數區別不一樣狀況以進行響應處理,方法註釋裏面都有介紹參數的做用,我這裏也簡單說明下。

powerOf2Scale: 是否爲2的冪數,也就是2.4.8.16.....
    true時即咱們以前教程裏面舉得例子,也是官方推薦的,最好縮放比例是2的冪數(2的N次方),須要經過循環每次*2的計算壓縮比例
    false即不須要是2的冪數,能夠是1.2.3.4...任何一個數,直接用圖片寬高除以所需圖片寬高便可得到壓縮比例(注意要是整數)
因此,true的時候和咱們教程(一)中的算法一致,而false的時候不作限定,那就能夠簡單的直接進行除法操做了


viewScaleType: 就是android裏控件imageView的scaleType屬性,這裏集合成了兩種,對應關係見框架的源碼,以下

public static ViewScaleType fromImageView(ImageView imageView) {
    switch (imageView.getScaleType()) {
    case FIT_CENTER:
    case FIT_XY:
    case FIT_START:
    case FIT_END:
    case CENTER_INSIDE:
        return FIT_INSIDE;
    case MATRIX:
    case CENTER:
    case CENTER_CROP:
    default:
        return CROP;
    }
}


再次重複

第一章的計算方法掌握了就能夠了,已經可以知足開發需求,下面這段是對於不一樣viewScaleType的處理分析,能夠跳過,有興趣的話就接着看看

---------------------------------------------------------------------

 

本段可略過~

這部份內容仍是很繞的,我也是邊研究邊寫,實例demo也一邊跑一邊debug看研究,基本上算是大概瞭解了。這一段反覆修改了屢次,耗費了大量精力,腦子有點迷亂了都,因此可能有不太準確的地方但願你們提醒下,我再作修改。


根據他的兩種不一樣算法(||和&&),咱們舉個具體例子說明下。
好比咱們限定圖片要200 200,原圖是1000 600,控件則是100 100的正方形(單位都是像素)。
先看默認的FIT_INSIDE效果
第一次循環,1000/2 >= 200 || 600/2 >= 200 都知足, 源寬高變成一半 500 400,縮放值翻倍,變成2~
第二次循環,500/2 >= 200 || 300/2 >= 200 知足其中一個,源寬高變成一半 250 200,縮放值翻倍,變成4~
第三次循環,250/2 >= 200 || 150/2 >= 200 都不知足,結束循環,最後縮放值爲 4
以縮放比例4計算,得到的縮放圖片實例的寬高即爲250 150


CROP
效果時的處理
第一次循環,1000/2 >= 200 && 600/2 >= 200 都知足,源寬高變成一半 500 400,縮放值翻倍,變成2~
第二次循環,500/2 >= 200 && 300/2 >= 200 只知足其中一個,沒有知足&&的條件,結束循環,最後縮放值是2~
以縮放比例2計算,得到的縮放圖片實例的寬高即爲500 300


這樣看的話,250 150的結果沒有知足寬高都大於等於限定值200 200的原則啊~
思考下壓縮圖片的原則,首先儘量的縮小,由於越小越節約內存,可是不能過小,由於壓縮過大的圖不夠清楚,效果很差~跟看騎兵電影似得,因此要有個最低值~
好比個人imageview控件的寬高是100 100大小的一個正方形,那我但願將顯示的圖片壓縮成至少爲200 200像素的大小,這樣我控件的單位大小上均可以顯示至少4個像素的圖片點~




因此限定大小,其實是爲了限定控件上圖片的像素密度,也就是控件單位大小上圖片像素點的數量~
對於控件正方形,圖片幾乎也是正方形的狀況,那就簡單了
好比一個500 500的圖片和一個200 200的圖片都放在100 100的imageview上~外觀看上去的大小都是同樣的,可是單位大小上的圖片像素點數量是不一樣的,500*500=250000,分佈在100*100的控件上,那控件單位大小上就有25個圖片像素點~
200 200的圖片,圖片像素密度則是4~ 確定是單位大小上圖片像素點越多就越清楚了




爲何要有區分處理呢,由於對於圖片長寬比例和控件長寬比例相差很大時,不一樣的scaleType形成的顯示縮放效果區別是很大的~
在長寬比相同或者相近時,好比都是正方形,那麼兩種顯示效果毫無區別,可是若是控件是正方形,圖片是3:2甚至3:1的矩形,那差異就明顯了, 咱們看下比例值差異較大時的
FIT_INSIDE的顯示效果和CROP_INSIDE


兩種顯示效果簡單的理解爲
     FIT_INSIDE(左圖)   完整的展現圖片,但imageView控件可能不佔滿
     CROP(右圖)          填充整個imageview,圖片可能會被裁去一部分


對於上面的例子1000 600的圖, 而圖片限定大小是200200
若是兩種效果都是用一種壓縮規則算法,好比官方提供的那種規則(也是咱們教程一里面的),則會把圖片壓縮成了500 300~
分析下壓縮後圖片在兩種scaleType下的縮放狀況


FIT_INSIDE模式時
顯示時候的縮放(不是圖片像素的壓縮)是根據較長的那個邊縮放的(藍色框框的上下兩條長邊),
500縮放成了100,縮放比例爲5,那像素密度就是25


CROP模式時
顯示時候的縮放是根據較短的那個段邊縮放的(藍色框框的左右倆短邊)
上例中是短邊是300,縮放成了100,比例爲3,那單位大小上圖片像素點就是9


預期大小是200 200,顯示在100 100的控件上其實就至關於但願單位大小上的圖片像素數量4以上




那個25貌似是有點多了呢~
那咱們按照UIL框架裏針對FIT_INSIDE的算法從新計算下,壓縮後圖片像素爲250 150
縮放類型按照長邊縮放,比例爲2.5~單位大小上圖片像素點是12.5~明顯知足條件又更加合適~


那CROP縮放類型也按照UIL框架FIT_INSIDE對應的算法處理呢?
算出來是250 150,顯示在CROP上,則是按照短邊縮放,比例是1.5~最後像素密度是2.25,不到4,明顯是不知足的了~




因此圖片限定寬高大小,是爲了保證圖片的清晰度,實質上是要保證單位大小上有足夠的像素點,即對像素密度有個最低值要求
根據長邊縮放的FIT_INSIDE效果,咱們只須要保證長的邊即其中一個大於等於限定數值便可
而根據短邊縮放的CROP效果,要保證短的邊大於等於限定數值即只有寬高都知足大於等於限定的數時才能夠
因爲大部分狀況下,尤爲是實際應用場景中,什麼頭像啊,logo啊,圖片啊大部分其實都是控件和圖片長寬比類似的,偶爾有幾個奇葩圖片也浪費不來多少內存的,因此通常沒有作區分處理~UIL框架這種區別計算比例更加的準確,固然,同時也更加難了


---------------------------------------------------------------------


圖片色彩樣式


縮放比例的控制介紹完畢,基本上和咱們教程(一)中介紹的方法差很少(沒羞沒臊啊), 只不過進行了一些優化
而對於圖片色彩樣式的控制,則能夠在框架提供的顯示配置對象中設置 
DisplayImageOptions.bitmapConfig(Bitmap.Config.RGB_565) 傳入所需色樣便可,默認一樣是ARGB_8888


---------------------------------------------------------------------


多張圖片的緩存池


單張圖片的縮放處理其實仍是比較簡單的(不考慮不一樣縮放效果區別處理的話)~
重點在於多張圖片的緩存池控制,下面進行介紹


首先是UIL的內存緩存
一共有八種內存緩存池類型,使用時只要選擇其中的一個便可(經過ImageLoaderConfiguration.memoryCache(...)設置)
咱們看下UIL在github上的主頁中對於各個內存緩存類型的具體介紹
(主頁地址見文章最開始處)
 

灰色框中標註的七種


只用了強引用,是如今官方推薦的方法,至關於咱們教程(二)裏面說的只用LruCache一個強引用緩存池的方式~也是框架的默認內存緩存方式
     LruMemoryCache    當超過緩存數量的極限值時刪除使用時間離得最遠的圖片(即LruCache的算法)


同時使用弱引用+強引用,也就是至關於教程(二)裏面說的二級緩存技術,區別在於這裏對其中的一級緩存即強引用緩存池部分的刪除算法進行了細分,採用了不一樣的規則,看下小括號後面的英文就知道了,我這裏簡單的翻譯下
    UsingFregLimitedMemoryCache 當超過緩存數量的極限值時刪除最不經常使用的圖片
    LruLimitedMemoryCache          當超過緩存數量的極限值時刪除使用時間離得最遠的圖片(即LruCache的算法)
    FIFOLimitedMemoryCache         當超過緩存數量的極限值時根據FIFO first in first out先入先出算法刪除
    LargestLimitedMemoryCache     當超過緩存數量的極限值時刪除最大的圖片
    LimitedAgeMemoryCache          當超過緩存數量的極限值時刪除超過存在最長時間的那個圖片(這個翻譯有可能不太準確= = )


只使用弱引用 因爲徹底沒有使用強引用,因此確定不會出現OOM異常,可是效率上捉雞~ UIL使用時基本上不多出現OOM異常的,真是人品爆發出現了,那解決辦法之一就是將內存緩存設置成這個只用弱引用的類型,由於沒有強引用部分~
     WeakmemoryCache    無限制的內存(系統根據弱引用規則自動回收圖片)




估計是框架很早之前就開始開發的問題,強引用部分用的是LinkedHashMap類,沒有采用LruCache類,但實際上處理邏輯基本都是同樣的~因爲LinkedHashMap的功能稍微弱一點~教程二里面能夠看到咱們是經過size()>閥值判斷的,也就是僅根據數量而不是根據全部圖片內存總大小,因此UIL框架中本身作了對應處理,在這個自定義個的強引用類型LruMemoryCache中設置了一個總內存size參數,每次put remove等操做時,都計算size的變化,而且在每次put操做時調用一個trimToSize方法,用於判斷添加後的緩存池大小,觀察size值是否超過了maxSize閥值,超過自定義的內存總大小值時則移除最老的圖片


代碼以下

package com.nostra13.universalimageloader.cache.memory.impl;

import android.graphics.Bitmap;

import com.nostra13.universalimageloader.cache.memory.MemoryCache;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * A cache that holds strong references to a limited number of Bitmaps. Each
 * time a Bitmap is accessed, it is moved to the head of a queue. When a Bitmap
 * is added to a full cache, the Bitmap at the end of that queue is evicted and
 * may become eligible for garbage collection.<br />
 * <br />
 * <b>NOTE:</b> This cache uses only strong references for stored Bitmaps.
 * 
 * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
 * @since 1.8.1
 */
public class LruMemoryCache implements MemoryCache {

    private final LinkedHashMap<String, Bitmap> map;

    private final int maxSize;
    /** Size of this cache in bytes */
    private int size;

    /**
     * @param maxSize
     *            Maximum sum of the sizes of the Bitmaps in this cache
     */
    public LruMemoryCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
    }

    /**
     * Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap
     * was returned, it is moved to the head of the queue. This returns null if
     * a Bitmap is not cached.
     */
    @Override
    public final Bitmap get(String key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        synchronized (this) {
            return map.get(key);
        }
    }

    /**
     * Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of
     * the queue.
     */
    @Override
    public final boolean put(String key, Bitmap value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        synchronized (this) {
            size += sizeOf(key, value);
            Bitmap previous = map.put(key, value);
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
        }

        trimToSize(maxSize);
        return true;
    }

    /**
     * Remove the eldest entries until the total of remaining entries is at or
     * below the requested size.
     * 
     * @param maxSize
     *            the maximum size of the cache before returning. May be -1 to
     *            evict even 0-sized elements.
     */
    private void trimToSize(int maxSize) {
        while (true) {
            String key;
            Bitmap value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator()
                        .next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= sizeOf(key, value);
            }
        }
    }

    /** Removes the entry for {@code key} if it exists. */
    @Override
    public final void remove(String key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        synchronized (this) {
            Bitmap previous = map.remove(key);
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
        }
    }

    @Override
    public Collection<String> keys() {
        synchronized (this) {
            return new HashSet<String>(map.keySet());
        }
    }

    @Override
    public void clear() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    /**
     * Returns the size {@code Bitmap} in bytes.
     * <p/>
     * An entry's size must not change while it is in the cache.
     */
    private int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    @Override
    public synchronized final String toString() {
        return String.format("LruCache[maxSize=%d]", maxSize);
    }
}

代碼分析
核心方法是put() 和trimToSize()

put方法:
synchronized修飾是爲了防止多線程訪問時形成size計算錯誤的,sizeOf是自定義方法,計算圖片size大小的,這裏首先將添加的圖片大小增至緩存池總大小size中。而後map.put方法添加圖片對象,返回值是若是map裏對應key已經有值了,則返回put前已有的那個對象~固然,put之後value已經被替換了,因此在+=新的圖片對象大小後,還要將前一個被替換掉的圖片size從總緩存池內存大小中減去,最後完成添加過程之後調用trimToSize方法查看添加操做完成後當前緩存池大小是否超過咱們自定義的閥值總大小maxSize


trimToSize方法:
無限循環直到當前緩存池大小size低於自定義閥值大小maxSize爲止~ 若是沒有知足size<=maxSize,即當前緩存池過大,則對map添加一個迭代器依次遍歷整個map從始至末挨個刪除,直到知足size<=maxSize結束循環~ 之因此用循環遍歷,是由於可能不止刪除一個圖片,好比最先添加的圖片只有5kb,而新圖片有15kb,那隻刪除最先一張圖片是不行的,可能添加進來一張須要擠出去多張。


每次put的時候hashMap都會將新對象添加至末端~key已存在時則替代對應的value對象並移到末端。而當迭代器遍歷的時候則是從始端開始遍歷的,也就等於個LRU的算法,刪除使用時間距今最老的圖片,則UIL框架自定義的這個強引用LRU緩存類,主要仍是對內存閥值size進行了處理和判斷。

框架裏的處理和LinkedHashMap的removeEldestEntry方法最終實現的效果都是同樣的。只不過具體代碼實現方式不一樣而已
這裏使用自定義方法而後進行處理,我的目測應該是爲了支持框架多重緩存池類型而設計的,畢竟LinkedHashMap的removeEldestEntry僅提供刪除最先使用對象的功能,而刪除最大圖片等這樣其餘的功能就不支持了。


以上是隻用強引用的緩存池部分,還算簡單,下面是二級緩存


二級緩存(弱+強)
這部分就凌亂了~因爲UIL框架中有5個弱+強的具體實現類(詳見上面的內存緩存介紹圖),因此基本功能實現都是在基類中進行的處理,實現的子類中僅針對算法進行了自定義規則而已~ 因爲代碼涉及到多層多個類== 本人的水平又有限,因此...就不仔細研究了,可能後續有時間的時候再補充到帖子裏。


大概思路是基類提供一個虛擬方法removeNext,返回bitmap值,而後在基類裏put方法後判斷強引用的size超過閥值時,對這個方法返回的bitmap對象刪除操做,而後將其移至弱引用池裏~ 具體移除規則則子類中自行編寫,你還能夠設計個按圖片size單雙數刪除圖片一類的特殊規則。


這就屬於設計思想了,能夠參考LinkedHashMap的removeEldestEntry源碼,能夠看到源碼中只使用,而方法內則是空的,就是留給開發者自行繼承重寫邏輯的。


本身如如有興趣開發框架,能夠根據本身階段性的水平來,開始的時候就直接用系統的LruCache寫就能夠了,二級緩存也很好處理。




注意:
咱們教程裏使用的是軟引用,這裏是弱引用。二者其實也差很少,弱引用生命週期比軟引用還低點。可是效率其實都通常,官方都不推薦使用了。
確定是要跟着官方權威信息走的,因此UIL框架的默認內存緩存方式用的也是LruMemoryCache類, 但同時又保留了其餘類型的緩存類, 框架使用者能夠根據實際須要在配置時經過ImageLoaderConfiguration.memoryCache(...)方法設置其餘提供的類型緩存

關於軟引用其餘那幾個類型算法FIFO等的具體實現,我這裏就不研究了,算法神馬的水平有限,你們有興趣能夠本身看看


此外還有一個FuzzyKeyMemoryCache的內存緩存類, UIL中緩存裏是支持保存同一張圖片的多種縮放比例緩存對象的,設置中也能夠取消這種設置使得緩存池中一個圖片只保留一個size的緩存對象,經過調用ImageLoader.denyCacheImageMultipleSizesInMemory()方法實現, 此時,UIL框架就會自動使用Fuzzy...去包裝一下你以前選擇的內存緩存類型。即算法仍是按照你選擇的類型,可是多了一個只保持一種size緩存對象的功能,這種設計叫作Decorator裝飾模式,舉個例子幫助理解下,這種設計模式至關於有一個變相怪傑的特殊面具,不管人或狗,誰帶上誰就能具備相應的牛逼能力。
算是一個特殊的內存緩存類型,不能單獨使用。



disk緩存

主要難點在於內存緩存,disk緩存其實比較簡單,就是圖片加載完成後把圖片文件存到本地方便下次使用。

一樣,先貼一下官方主頁的介紹。和內存緩存差很少,根據算法不一樣提供了幾種類別,能夠自行經過ImageLoaderConfiguration.discCache(..)設置


硬盤緩存,保存是以文件的形式。框架提供了4種類型,具體算法規則不一樣,看名字咱們大概也能知道對應意思

UnlimitedDiscCache                  不限制緩存池大小,最快的一種disk緩存, 默認硬盤緩存方式(比其餘disk緩存方式快30%)
TotalSizeLimitedDiscCache      限制緩存池總size大小,超出時刪除最先保存的圖片緩存文件
FileCountLimitedDiscCache      限制緩存池總數量,超出時刪除最先保存的圖片緩存文件,緩存圖片是相同大小時使用此disk緩存類型
LimitedAgeDiscCache               不限制緩存池的大小,只對文件保存時間作限制,若是圖片文件超出定義時間時刪除之

一樣,具體算法不作討論~

圖片緩存文件位置是 優先保存在 內存卡下的Android\data\應用包名\cache文件夾下的
(無權限,沒有裝sd等 沒法保存至sd卡的狀況時,則保存在 手機內存data\data\應用包名\cache文件夾下)
下圖能夠看到,文件緩存的保存位置是區分應用的,每一個應用設置一個緩存文件夾,圖片文件的名字也通過了md5編碼處理
 



--------------------------------------------------------------------------------------------------------


圖片加載一些具體數值的設置
比較重要的好比限定圖片寬高多少合適啊~緩存池大小限定多少啊等等。UIL框架中是在 基本配置對象中進行設置的,前面提到過,有一個默認設置ImageLoaderConfiguration.createDefault(this);針對絕大部分狀況都適用的,要修改一些經常使用的配置的話,設置方法能夠去github UIL主頁下載(文章開頭有地址,文章結尾處我也會加上附件的)壓縮包,裏面包括源代碼以及示例代碼,能夠在示例demo中查看一些經常使用基本配置設置和顯示配置的設置


這裏只介紹幾個重要的值設定


圖像壓縮後的限定寬高值
 

不一樣的限定值設定,是有一個優先級的。我專門實驗研究了下(actual measured width and height不是太清楚)
優先級  memoryCacheExtraOpstion> width/height > maxWidth/maxHeight > divice screen

即由高到低依次遍歷是否能獲取到值,得到後做爲限定寬高值解析壓縮後的圖片實例對象

 

 



緩存池大小的設置~
分強引用和弱引用,弱引用咱們知道不用限制~主要是針對強引用部分緩存池的限制
教程(二)裏面也提到過,主要分兩種:限制圖片數量 和 限制所有圖片所佔內存大小
對於強引用大小的限定,咱們看下UIL框架的默認處理

/**
 * Creates default implementation of {@link MemoryCache} -
 * {@link LruMemoryCache}<br />
 * Default cache size = 1/8 of available app memory.
 */
public static MemoryCache createMemoryCache(int memoryCacheSize) {
    if (memoryCacheSize == 0) {
        memoryCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8);
    }
    return new LruMemoryCache(memoryCacheSize);
}

獲取程序可用內存的1/8做爲緩存池大小,也是推薦設置,有須要能夠調用上面的方法傳入>0的自定義size值便可設置。

限制圖片總數量,恕我眼神拙計= = 源碼中沒找到默認設置,畢竟設置內存大小更合適點

若是本身項目中有特殊須要,能夠參考教程(二)中的相關部分進行設置

 

 

此外還有disk緩存池,因爲默認是無限制disk緩存類型,沒有一些具體參數數值的設定了~

還有圖片下載線程數量問題,disk圖片取名問題等等,不是核心部分~這裏篇幅有限就不介紹了

 

--------------------------------------------------------------------------------------------------------

 


以上,核心代碼分析結束~都弄懂了的話,圖片的處理已經算是小有所成了,好處嘛~
一方面在使用框架的時候你能夠更瞭解內部的機制,適當時候能夠根據項目具體須要作相關繼承重寫進行修改(最好不要直接修改開源框架源碼,保證其完整性)
另外一方面更能夠看成本身的一個優點點亮點,尤爲在面試時多發揮發揮~好比介紹本身項目時,大部分網絡數據交互的項目通常都是會有列表多圖加載的狀況的,你就能夠就圖片加載部分大談特談了~不過聊框架部分有風險,對於此方面知識匱乏的面試官來講, 有時候不明也不必定會覺歷,會白白浪費口水~最好結合項目一邊演示一邊談,具體技巧這裏就不班門弄斧了,能夠網上找些帖子看




下面的內容一樣是選擇性觀看
包括UIL框架的一些擴展功能,以及源碼的結


--------------------------------------------------------------------------------------------------------


UIL框架在覈心的圖片緩存技術,圖片壓縮,異步線程下載圖片的傳統框架功能之外~
還作了一些其餘的
拓展功能
自定義ImageView控件,叫作ImageAware,支持圓角效果等功能
disk保存圖片文件名md5編碼
圖片加載的顯示效果,好比淡入的fade效果等等
(此外還有一些擴展的功能,因爲是非核心部分不作詳細介紹了)
此外還提供Widget~ 這個我就沒研究了


源碼結構
主要分三大包~下面挨個介紹


UIL的緩存池部分包/類結構
見下圖,很清楚的結構,cache緩存包下分紅兩個部分,disc(disk緩存)包和memory(內存緩存)包
綠色部分是基類父類~ impl是具體實現子類,好比memory的impl包下的那8個,就是咱們以前介紹的能夠直接使用的不一樣緩存池類型,項目中有特殊需求的話,也能夠自定義一個類繼承重寫作對應的修改
disk同理,基類+具體實現類,在此基礎上多了個naming命名包,是對圖片保存文件名稱進行md5編碼解碼處理的

 







核心包
其中着重研究紅框部分
    圖片解析包decode
    基本配置類ImageLoaderCofiguration
    顯示配置類DisplayImageOptions
    和最重要的圖片加載器類ImageLoder
其餘部分
assist爲助手包,裏面大部分都是一些Enum枚舉類,好比前面提到的圖片縮放類型,圖片壓縮類型等
display圖片顯示包,框架提供圓角矩形的圖片顯示效果,以及加載圖片時fade效果(從透明到實體慢慢浮現的感受)等
download圖片下載包,篇幅有限此功能這裏不作分析
imageaware自定義圖片控件包,提供圓角的效果等
listener監聽包,圖片開始加載,正在加載,加載完畢等監聽
process進程包,能夠繼承裏面的接口自行定義圖片加載過程當中的效果
其餘的還有一些線程啊,默認配置文件生成類啊等等的,一樣篇幅有限不作分析
 



工具包
提供一些所需功能,好比IO流處理,disk緩存的路徑處理等
其中最重要的是紅框所示的圖片size工具類,好比前面提到的computeImageSampleSize計算縮放比例就是在這個類裏
 




--------------------------------------------------------------------------------------------------------





寫了大概將近一週,修改時間佔了一大半,反反覆覆的改,也是由於我的技術有限,部分姿式也是邊研究邊寫的,可能有不太詳細或者錯誤的地方再次但願你們提出來,多多指正或者共同探討一下,也但願你們能收藏一下,我以後也會繼續潤色或補充文章不足地方的


UIL功能強大是毋庸置疑的,代碼框架也很清晰,文檔也算齊全~ 可是對開發者尤爲是我這樣的初學者來講一點點啃下來仍是很艱難的,最好先看教程一二,徹底懂了之後再看本篇,固然一二看懂基本上圖片處理也差很少了


本篇至關於對以前教程作個驗證 "你看,最有名的框架基本上也是這麼處理嘛~"如此這般從側面證明一下教程中方法的靠譜性~

其餘做用呢? 經過鑽研確定是提升咱們的技術了,學習別人的類結構設計~ 也能更好的使用圖片框架(其餘框架差很少都這樣邏輯),且對於拓展部分也能夠做爲本身的亮點在面試中使用 ~


(四)開源圖片框架分析2-ImageLoader和Volley

懶得複製+調整格式了,地址:http://www.eoeandroid.com/thread-334315-1-1.html


(五)圖片處理效果對比

對比對象: UIL Volley 官方教程中的方法(此係列教程一里介紹的,ImageLoader的處理方法和官方的差很少)

------------------------------------------------------------------------

首先 單張圖片的壓縮處理,也是分析重點
專門擼了一個小demo(結尾會放出下載鏈接)將對應計算方法copy了出來,而後計算了幾十組數據,進行了對比
原圖寬高都是一個10000之內的隨機整數,限定大小是400 200,而後進行壓縮處理,記錄了10組數據以下
 

控件縮放類型FIT_INSIDE和CROP在教程三-1的UIL框架分析中有詳細說明,這裏再簡單說下
FIT_INSIDE是默認控件顯示類型,圖片會完整的顯示在控件內,不必定會填滿控件
CROP是使圖片填充整個控件,圖片可能會顯示不全
具體效果你們能夠在android項目裏建一個imageview不設置縮放類型(即FIT_INSIDE效果), 再建一個scaleType爲CROP的同大小控件,而後顯示同一張圖片,就會看出來區別了


官方/volley/imageloader都沒有對縮放類型作區別處理,因此不一樣縮放類型下,最終壓縮圖片是一個size
結果咱們也能夠明顯看出
官方算法是和UIL的CROP類型時結果同樣的,實質上做用是保證壓縮後圖片寬高 都要大於等於限定寬高,
Volley的那種對限定值的特殊處理方式,則實質上最終的效果是讓壓縮後的圖片寬高 任意一個大於等於限定寬高便可,則對應UIL的FIT_INSIDE效果


哪一種好呢?(只針對圖片的壓縮處理而言)
UIL咱們已經長篇詳細的分析過了(教程系列三-1),對於縮放類型的處理是十分準確的, 明顯是最好的~(這麼多人用不是沒道理的)
那對比起來,官方的處理和Volley的則各有不足了~

官方=UIL CROP 即官方的處理方法其實更適合於CROP縮放類型的圖片顯示
然而在處理寬高比差異較大的圖片時,若是是FIT_INSIDE顯示模式,則會形成壓縮圖片略大,雖然能保證顯示質量,可是浪費小部份內存資源
好比上圖中的第一組數據,664 3640的壓縮結果,明顯和咱們所需的400 200差異過大- - 腦補一下大概就是火柴盒跟西瓜刀吧(不是太準確~)

Volley=UIL FIT_INSIDE 則表明Volley更適合默認狀況下的圖片顯示狀況了
那麼在處理寬高比差異過大的圖片時,若是是CROP縮放類型,則壓縮大小看起來是差很少了,但實際上顯示效果是沒法達到預期的,大小咱們能夠從size數值上看出來,顯示效果我仍是弄個實際圖片你們對比看看吧(能夠在文章末尾下載demo項目)


喪心病狂的找了個微博長圖實驗,項目一共四個ImageView控件,上面倆是Volley,下排是UIL,左邊倆是FIT_INSIDE效果,右邊則是CROP
而我說的Volley在過大寬高差時,用CROP類型顯示沒法達到預期質量的效果,就是右上角這個圖了,簡直就是我不戴眼鏡看世界滴樣子
對比下,UIL在處理CROP狀況時效果十分有保障~
 

上面缺點都有個條件 寬高比差異過大,由於大部分狀況下,原圖的寬高比是比較穩定的,而咱們限定值也都是差很少的,大部分狀況都是設爲一個正方形的限定大小,原圖基本也都是接近正方形的矩形~這就解釋了爲何大部分的圖片框架包括官方教程中都沒有專門對不一樣縮放類型作區分處理,由於通常狀況下,即長寬比穩定差異不大的狀況下,官方的那種簡單處理都是適用滴
參考上面數據咱們也能夠得出,當原圖長寬比和限定長寬比越相近,則兩種不一樣算法(官方和UIL的CROP是一種,Volley和UIL的FIT_INSIDE是另外一種)的區別越小,當原圖和限定值的長寬比十分類似甚至相等時,那兩種算法最終的結果就同樣了(好比數據中紅色加粗的部分)
而咱們看長寬比差異較大的第一組數據和倒數第二組數據,兩種算法的差異直接是2^4=16倍~你們有興趣能夠自行嘗試,弄幾個更大比例的特殊值試一試

------------------------------------------------------------------------

圖片色彩樣式對比
Volley寫死了是RGB_565
ImageLoader沒有設置,那應該就是默認的ARGB_8888
UIL提供設置API,默認則也是ARGB_8888

------------------------------------------------------------------------

緩存池對比
Volley只有接口...實例類都沒有,不過寫起來也很簡單
ImageLoader則主要是單強引用,單軟引用兩種
UIL提供了8種類型,弱和強,單獨以及混合的,包括不一樣的算法

區別就在於軟引用和弱引用的不一樣,這個直接看教程二里面針對不一樣引用類型的介紹就好了,也能夠自行百度搜,其實二者區別不大
強引用部分也都差很少,即便使用了最新的LruCache類,其實看源碼咱們也會發現內部仍是用LinkedHashMap實現的

------------------------------------------------------------------------


綜上,確定是UIL徹底勝出了,還有不少拓展功能部分也都是UIL更增強大沒跑了
不過Volley框架咱們以前也說過了,雖然功能不夠完善,但十分適合咱們二次開發
ImageLoader嗎,稍微簡單點,能夠當學習源碼的小練手來用


------------------------------------------------------------------------

最後滴最後,demo簡單介紹下
drawable包下有一個長寬比很大的微博長圖,還有一個正常的接近正方形的圖,
代碼只有一個類,裏面把幾種壓縮算法都貼出來了,測試方法也已經寫好,你們能夠自行修改數值計算,也能夠拷貝一些圖片進去看其餘比例圖片的效果
佈局文件對應也只有一個,裏面放了4個imageView,倆普通的,倆center_crop的,也就是對應的CROP縮放類型,控件縮放類型和UIL框架縮放類型的對應關係,參考教程三-1的UIL介紹裏


連接: http://pan.baidu.com/s/1eQswfcu 密碼:na94
相關文章
相關標籤/搜索