項目中經常碰到需要處理大圖片的問題,因為android對應用程式指派資源的限制,如果不進行相應的處理,容易造成OOM。
Android處理大圖的方法:
對於大圖先擷取出圖片的width和height, 然後根據view的width和height, 換算出圖片inSampleSize,,最後壓縮產生相應的圖片。
其中重要的兩個參數是:
/**111 * If set to true, the decoder will return null (no bitmap), but112 * the out... fields will still be set, allowing the caller to query113 * the bitmap without having to allocate the memory for its pixels.114 */115 public boolean inJustDecodeBounds;116117 /**118 * If set to a value > 1, requests the decoder to subsample the original119 * image, returning a smaller image to save memory. The sample size is120 * the number of pixels in either dimension that correspond to a single121 * pixel in the decoded bitmap. For example, inSampleSize == 4 returns122 * an image that is 1/4 the width/height of the original, and 1/16 the123 * number of pixels. Any value <= 1 is treated the same as 1. Note: the124 * decoder uses a final value based on powers of 2, any other value will125 * be rounded down to the nearest power of 2.126 */127 public int inSampleSize;
InJustDecodeBounds能在不分配資源給圖片的情況下擷取圖片的大小
inSampleSize計算圖片的壓縮比
開源項目xutils, Universal-Image-Loader和Foursquare對於大圖的處理都類似的
下面是Foursquare處理大圖的代碼:
public class ImageUtils { private ImageUtils() { } public static void resampleImageAndSaveToNewLocation(String pathInput, String pathOutput) throws Exception { Bitmap bmp = resampleImage(pathInput, 640); OutputStream out = new FileOutputStream(pathOutput); bmp.compress(Bitmap.CompressFormat.JPEG, 90, out); } public static Bitmap resampleImage(String path, int maxDim) throws Exception { BitmapFactory.Options bfo = new BitmapFactory.Options(); bfo.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, bfo); BitmapFactory.Options optsDownSample = new BitmapFactory.Options(); optsDownSample.inSampleSize = getClosestResampleSize(bfo.outWidth, bfo.outHeight, maxDim); Bitmap bmpt = BitmapFactory.decodeFile(path, optsDownSample); Matrix m = new Matrix(); if (bmpt.getWidth() > maxDim || bmpt.getHeight() > maxDim) { BitmapFactory.Options optsScale = getResampling(bmpt.getWidth(), bmpt.getHeight(), maxDim); m.postScale((float)optsScale.outWidth / (float)bmpt.getWidth(), (float)optsScale.outHeight / (float)bmpt.getHeight()); } int sdk = new Integer(Build.VERSION.SDK).intValue(); if (sdk > 4) { int rotation = ExifUtils.getExifRotation(path); if (rotation != 0) { m.postRotate(rotation); } } return Bitmap.createBitmap(bmpt, 0, 0, bmpt.getWidth(), bmpt.getHeight(), m, true); } private static BitmapFactory.Options getResampling(int cx, int cy, int max) { float scaleVal = 1.0f; BitmapFactory.Options bfo = new BitmapFactory.Options(); if (cx > cy) { scaleVal = (float)max / (float)cx; } else if (cy > cx) { scaleVal = (float)max / (float)cy; } else { scaleVal = (float)max / (float)cx; } bfo.outWidth = (int)(cx * scaleVal + 0.5f); bfo.outHeight = (int)(cy * scaleVal + 0.5f); return bfo; } private static int getClosestResampleSize(int cx, int cy, int maxDim) { int max = Math.max(cx, cy); int resample = 1; for (resample = 1; resample < Integer.MAX_VALUE; resample++) { if (resample * maxDim > max) { resample--; break; } } if (resample > 0) { return resample; } return 1; } public static BitmapFactory.Options getBitmapDims(String path) throws Exception { BitmapFactory.Options bfo = new BitmapFactory.Options(); bfo.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, bfo); return bfo; }}
還有一個問題需要糾正:
看了幾篇關於處理大圖的文章都說不要調用BitmapFactory.decodeResource這個函數,因為這個函數在完成decode後,最終都是通過java層的createBitmap來完成的,需要消耗更多記憶體。
我看了BitmapFactory的源碼發現上面那個分析是錯誤的,decodeResource最終也是調用Jni去擷取圖片。請看下面時序圖。