Bitmap是Android應用程序引起OOM的罪魁禍首之一,當我們從網絡上下載圖片的時候無法知道網絡圖片的准確大小,所以為了節約內存,一般會在服務器上緩存 一個縮略圖,提升下載速度。除此之外,我們還可以在本地顯示圖片前將圖片進行壓縮,使其完全符合imageview的大小,這樣可以最大限度避免內存浪費。
本文基本思路:
(1)獲取ImageView的寬和高。
(2)使用inJustDecodeBounds獲取bitmap的長和寬。
(3)根據bitmap的長款和ImageView的長和寬,計算出壓縮比例inSampleSize的大小。
(4)使用inSampleSize,加載一個比ImageView稍大一點的縮略圖a。
(5)使用Bitmap.createScaseBitmap再次壓縮A,將縮略圖A生成需要的縮略圖B。
1.計算ImageView的寬高
int ImageView.getWidth() int ImageView.getHeight()
這兩個方法可以得到ImageView的實際大小,單位為pix,需要在view加載完之后再調用。如果不知道什么時候加載完,可以使用下面方法在run()中獲取:
final ImageView iv = new ImageView(context); iv.post(new Runnable() { @Override public void run() { int w = iv.getWidth(); int h = iv.getHeight(); } })
2.通過BitmapFactory計算得到壓縮后的bitmap對象。
2.1 BitmapFactory.Options介紹
BitmapFactory提供了多種方式根據不同的圖片源來創建Bitmap對象:
(1)Bitmap BitmapFactory.decodeFile(String pathName, Options opts): 讀取SD卡上的圖片。
(2)Bitmap BitmapFactory.decodeResource(Resources res, int id, Options opts) : 讀取網絡上的圖片。
(3)Bitmap BitmapFactory.decodeResource(Resources res, int id, Options opts) : 讀取資源文件中的圖片。
這三個函數默認會直接為bitmap分配內存,我們通過第二個參數Options來控制,讓它不分配內存,同時可以得到圖片的相關信息。具體參考:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathName,options); int imageHeight = options.outHeight; int imageWidth = options.outWidth;
這樣就可以在不分配內存的情況下直接得到圖片的寬高。
2.2 計算圖片壓縮比例(采樣率)
通常如果圖片是針對某種分辨率設計的,直接decode圖片不會有什么問題。但是,如果圖片不是特定設計,且比較大的話,容易造成OOM。
假設,一個ImageView大小為512*384,有一張2048*1536的圖片需要顯示,那么如果加載整張圖片,那就造成了浪費,這時候就需要采用不同的采樣率對原圖片進行一定的壓縮。
計算inSampleSize的值有兩種方法:
方法一:
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 源圖片的高度和寬度 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // 計算出實際寬高和目標寬高的比率 final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // 選擇寬和高中最小的比率作為inSampleSize的值,這樣可以保證最終圖片的寬和高 // 一定都會大於等於目標的寬和高。 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; }
方法二:
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 源圖片的高度和寬度 final int height = options.outHeight; final int width = options.outWidth; 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; }
需要注意的是,inSampleSize只能是2的整數次冪,如果不是的話,向下取得最大的2的整數次冪。因為按照2的次方進行壓縮會比較高效和方便。 方法一計算出來的inSampleSize值可能不是2的整數次冪,不如計算出來的值是inSampleSize=7,這時會被decode函數向下取為 inSampleSize=4。所以我們一般采用方法二進行計算。
此時,將設置了inSampleSize的options傳給BitmapFactory.decode函數去獲取圖片,得到的會是一張比ImageView稍大的圖片,不過這個圖片要比原圖小了。
public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathName, options); options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; Bitmap src = BitmapFactory.decodeFile(pathName, options); return createScaleBitmap(src, reqWidth, reqHeight); }
2.3 得到符合ImageView大小的圖片
這是通過Bitmap Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)函數來實現的。
這個函數返回一個按要求進行拉伸或者縮小后的bitmap.
private static Bitmap createScaleBitmap(Bitmap src, int dstWidth, int dstHeight, int inSampleSize) { // 如果是放大圖片,filter決定是否平滑,如果是縮小圖片,filter無影響,我們這里是縮小圖片,所以直接設置為false Bitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false); if (src != dst) { // 如果沒有縮放,那么不回收 src.recycle(); // 釋放Bitmap的native像素數組 } return dst; }
