在Android的開發中,咱們常常回去處理一些圖片相關的問題,好比當加載圖片到內存中產生的OOM(OutOfMemory)異常、圖片加載到內存中佔多大內存的問題、jpg png兩種常見的圖片的原理及區別。 git
圖片加載到內存所佔內存大小的問題github
在講OOM異常前須要對圖片的加載有所瞭解,因此在這裏就先介紹圖片加載的問題。 緩存
圖片加載到內存中的大小,不是直接由圖片的存儲大小來決定的。好比一個10k大小的png格式的圖片加載到內存可能就不止10k了。那應該怎麼計算呢?網絡
圖片加載到內存中的大小=圖片的寬×圖片的高×該圖片一個像素所佔的位數/8 框架
舉個例子:一個1024*1024像素的圖片,每一個像素是32位,那麼他的大小就是1024×1024×32÷8=4M。一般圖片保存成jpg、png格式是通過壓縮處理的,它的存儲大小可能就只有幾k。這就是爲何咱們在加載一個10多k的圖片是會出現OOM異常的緣由。ide
加載較大的圖片函數
在展現高分辨率圖片的時候,最好先將圖片進行壓縮。壓縮後的圖片大小應該和用來展現它的控件大小相近,在一個很小的ImageView上顯示一張超大的圖片不會帶來任何視覺上的好處,但卻會佔用不少的內存,並且在性能上還可能會帶來負面影響。下面咱們就來看一看,如何對一張大圖片進行適當的壓縮,讓它可以以最佳大小顯示的同時,還能防止OOM的出現。 性能
BitmapFactory這個類提供了多個解析方法(decodeByteArray, decodeFile, decodeResource等)用於建立Bitmap對象,咱們應該根據圖片的來源選擇合適的方法。好比SD卡中的圖片可使用decodeFile方法,網絡上的圖片可使用decodeStream方法,資源文件中的圖片可使用decodeResource方法。這些方法會嘗試爲已經構建的bitmap分配內存,這時就會很容易致使OOM出現。爲此每一種解析方法都提供了一個可選的BitmapFactory.Options參數,將這個參數的inJustDecodeBounds屬性設置爲true就可讓解析方法禁止爲bitmap分配內存,返回值也再也不是一個Bitmap對象,而是null。雖然Bitmap是null了,可是BitmapFactory.Options的outWidth、outHeight和outMimeType屬性都會被賦值。這個技巧讓咱們能夠在加載圖片以前就獲取到圖片的長寬值和MIME類型,從而根據狀況對圖片進行壓縮。以下代碼所示:spa
1 BitmapFactory.Options options = new BitmapFactory.Options(); 2 options.inJustDecodeBounds = true; 3 BitmapFactory.decodeResource(getResources(), R.id.myimage, options); 4 int imageHeight = options.outHeight; 5 int imageWidth = options.outWidth; 6 String imageType = options.outMimeType;
在加載圖片時,最好每次都先檢查一下圖片的大小,除非你能確保這個圖片不會致使OOM異常。 code
經過上面的代碼咱們能獲得圖片的大小,下面咱們來對圖片進行壓縮處理。經過設置BitmapFactory.Options中inSampleSize的值就能夠實現。好比咱們有一張2048*1536像素的圖片,將inSampleSize的值設置爲4,就能夠把這張圖片壓縮成512*384像素。本來加載這張圖片須要佔用13M的內存,壓縮後就只須要佔用0.75M了(假設圖片是ARGB_8888類型,即每一個像素點佔用4個字節)。下面的方法能夠根據傳入的寬和高,計算出合適的inSampleSize值:
1 public static int calculateInSampleSize(BitmapFactory.Options options, 2 int reqWidth, int reqHeight) { 3 // 源圖片的高度和寬度 4 final int height = options.outHeight; 5 final int width = options.outWidth; 6 int inSampleSize = 1; 7 if (height > reqHeight || width > reqWidth) { 8 // 計算出實際寬高和目標寬高的比率 9 final int heightRatio = Math.round((float) height / (float) reqHeight); 10 final int widthRatio = Math.round((float) width / (float) reqWidth); 11 // 選擇寬和高中最小的比率做爲inSampleSize的值,這樣能夠保證最終圖片的寬和高 12 // 必定都會大於等於目標的寬和高。 13 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; 14 } 15 return inSampleSize; 16 }
使用這個方法,首先你要將BitmapFactory.Options的inJustDecodeBounds屬性設置爲true,解析一次圖片。而後將BitmapFactory.Options連同指望的寬度和高度一塊兒傳遞到到calculateInSampleSize方法中,就能夠獲得合適的inSampleSize值了。以後再解析一次圖片,使用新獲取到的inSampleSize值,並把inJustDecodeBounds設置爲false,就能夠獲得壓縮後的圖片了。
大量圖片的緩存處理
在你應用程序的UI界面加載一張圖片是一件很簡單的事情,可是當你須要在界面上加載一大堆圖片的時候,狀況就變得複雜起來。在不少狀況下,(好比使用ListView, GridView 或者 ViewPager 這樣的組件),屏幕上顯示的圖片能夠經過滑動屏幕等事件不斷地增長,最終致使OOM。
爲了保證內存的使用始終維持在一個合理的範圍,一般會把被移除屏幕的圖片進行回收處理。此時垃圾回收器也會認爲你再也不持有這些圖片的引用,從而對這些圖片進行GC操做。用這種思路來解決問題是很是好的,但是爲了能讓程序快速運行,在界面上迅速地加載圖片,你又必需要考慮到某些圖片被回收以後,用戶又將它從新滑入屏幕這種狀況。這時從新去加載一遍剛剛加載過的圖片無疑是性能的瓶頸,你須要想辦法去避免這個狀況的發生。
這個時候,使用內存緩存技術能夠很好的解決這個問題,它可讓組件快速地從新加載和處理圖片。下面咱們就來看一看如何使用內存緩存技術來對圖片進行緩存,從而讓你的應用程序在加載不少圖片的時候能夠提升響應速度和流暢性。
咱們可使用LruCache來處理這個問題,例子以下:
1 private LruCache<String, Bitmap> mMemoryCache; 2 3 @Override 4 protected void onCreate(Bundle savedInstanceState) { 5 // 獲取到可用內存的最大值,使用內存超出這個值會引發OutOfMemory異常。 6 // LruCache經過構造函數傳入緩存值,以KB爲單位。 7 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 8 // 使用最大可用內存值的1/8做爲緩存的大小。 9 int cacheSize = maxMemory / 8; 10 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 11 @Override 12 protected int sizeOf(String key, Bitmap bitmap) { 13 // 重寫此方法來衡量每張圖片的大小,默認返回圖片數量。 14 return bitmap.getByteCount() / 1024; 15 } 16 }; 17 } 18 19 public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 20 if (getBitmapFromMemCache(key) == null) { 21 mMemoryCache.put(key, bitmap); 22 } 23 } 24 25 public Bitmap getBitmapFromMemCache(String key) { 26 return mMemoryCache.get(key); 27 }
另外,在github上有個很好用的解決大量圖片緩存的框架—xUtils,其中BitmapUtils模塊就是用來解決這個問題的。