整理自帖子:http://www.eoeandroid.com/thread-331669-1-1.htmlhtml
/** * 獲取壓縮後的圖片 * @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); }
/** * 計算壓縮比例值 * @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; }
通常少許圖片是不多出現OOM異常的,除非單張圖片過大。那麼就能夠用第一節裏面的方法了。
一般應用場景是listview列表加載多張圖片,爲了提升效率通常要緩存一部分圖片,這樣方便再次查看時能快速顯示,不用從新下載圖片。可是手機內存是頗有限的,當緩存的圖片愈來愈多,即便單張圖片不是很大,不過數量太多時仍然會出現OOM的狀況了。前端
代碼實現
軟應用緩存池類型做爲二級緩存:
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
摘取段對軟引用的介紹
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有一個有效的回收機制,讓用戶可以調整有多少內存分配。
@Override protected int sizeOf(String key, Bitmapvalue) { return value.getRowBytes() * value.getHeight(); }
主要介紹這三個框架,都挺有名的,其餘的框架估計也差很少了
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對象。大部分框架其實都這一個尿性,配置稍微麻煩點,可是使用時通常只須要一行,顯示方法通常會提供多個重載方法,支持不一樣須要。
因爲不是框架使用教程,因此下面結合以前兩章的內容着重分析下框架對於單張圖片的壓縮處理,和多圖緩存池的處理
---------------------------------------------------------------------
面試
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裝飾模式,舉個例子幫助理解下,這種設計模式至關於有一個變相怪傑的特殊面具,不管人或狗,誰帶上誰就能具備相應的牛逼能力。
算是一個特殊的內存緩存類型,不能單獨使用。
即由高到低依次遍歷是否能獲取到值,得到後做爲限定寬高值解析壓縮後的圖片實例對象
緩存池大小的設置~
分強引用和弱引用,弱引用咱們知道不用限制~主要是針對強引用部分緩存池的限制
教程(二)裏面也提到過,主要分兩種:限制圖片總數量 和 限制所有圖片所佔內存大小
對於強引用大小的限定,咱們看下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功能強大是毋庸置疑的,代碼框架也很清晰,文檔也算齊全~ 可是對開發者尤爲是我這樣的初學者來講一點點啃下來仍是很艱難的,最好先看教程一二,徹底懂了之後再看本篇,固然一二看懂基本上圖片處理也差很少了
本篇至關於對以前教程作個驗證 "你看,最有名的框架基本上也是這麼處理嘛~"如此這般從側面證明一下教程中方法的靠譜性~
其餘做用呢? 經過鑽研確定是提升咱們的技術了,學習別人的類結構設計~ 也能更好的使用圖片框架(其餘框架差很少都這樣邏輯),且對於拓展部分也能夠做爲本身的亮點在面試中使用 ~
懶得複製+調整格式了,地址:http://www.eoeandroid.com/thread-334315-1-1.html
(五)圖片處理效果對比