?对于很多处理图形相关的Android开发者来说,大的Bitmap对象可能直接导致软件崩溃,Android平台如何防止内存泄露呢? 目前来说Android设备的RAM可能差距比较大,很多低端配置的256MB RAM或512MB RAM由于运行了太多的后台任务或HTC Sense这样的主题导致了处理一些高像素的图片,比如500w或800w像素的照片很容易崩溃。
? 1. 判断目标设备Dalvik VM内存情况
? 通过 java.lang.Runtime类的?long? freeMemory() 方法可以获取当前进程的RAM可用情况,Runtime类需要? getRuntime() 方法来实例化。
?比如获取最大可用RAM 为 Runtime.getRuntime().maxMemory();
? 2. Bitmap对象在打开时可以考虑先缩小图片
? 通过减少工作区域可以有效的降低RAM使用,由于在内存中是DIB方式,可以想象ARGB的图像占用内存为 4*height*width,比如500万像素的图片,占用内存就是500x4=2000万字节就是19MB左右。同时Java VM的异常处理机制和绘图方法可能在内部产生副本,最终消耗的运行内存是十分庞大的,对于图片打开时就进行缩小可以使用 android.graphics.BitmapFactory的相关方法来处理,这里参考Android123早期文章,Android缩略图类源代码?即可
?3. 及时的显示执行Bitmap的recycle方法,以及是当时可以调用Runtime的gc方法,提示虚拟机尽快释放掉内存
?
Android 2.2开始新增的缩略图类ThumbnailUtils的主要方法是静态的,对于Android 2.2或API Level8以下的工程可以直接使用,本类相对于我们常规的缩略图类考虑更周全,除了尺寸比例优化外,针对OOM的内存管理方面有更周全的处理方式,完整代码如下,使用方法和介绍请查看 ThumbnailUtils - Android2.2新增类?一文
public class ThumbnailUtils {
??? private static final String TAG = "ThumbnailUtils";
??? /* Maximum pixels size for created bitmap. */
??? private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384;
??? private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 128 * 128;
??? private static final int UNCONSTRAINED = -1;
??? /* Options used internally. */
??? private static final int OPTIONS_NONE = 0x0;
??? private static final int OPTIONS_SCALE_UP = 0x1;
??? /**
???? * Constant used to indicate we should recycle the input in
???? * [email protected] #extractThumbnail(Bitmap, int, int, int)} unless the output is the input.
???? */
??? public static final int OPTIONS_RECYCLE_INPUT = 0x2;
??? /**
???? * Constant used to indicate the dimension of mini thumbnail.
???? * @hide Only used by media framework and media provider internally.
???? */
??? public static final int TARGET_SIZE_MINI_THUMBNAIL = 320;
??? /**
???? * Constant used to indicate the dimension of micro thumbnail.
???? * @hide Only used by media framework and media provider internally.
???? */
??? public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96;
??? /**
???? * This method first examines if the thumbnail embedded in EXIF is bigger than our target
???? * size. If not, then it'll create a thumbnail from original image. Due to efficiency
???? * consideration, we want to let MediaThumbRequest avoid calling this method twice for
???? * both kinds, so it only requests for MICRO_KIND and set saveImage to true.
???? *
???? * This method always returns a "square thumbnail" for MICRO_KIND thumbnail.
???? *
???? * @param filePath the path of image file
???? * @param kind could be MINI_KIND or MICRO_KIND
???? * @return Bitmap
???? *
???? * @hide This method is only used by media framework and media provider internally.
???? */
??? public static Bitmap createImageThumbnail(String filePath, int kind) {
??????? boolean wantMini = (kind == Images.Thumbnails.MINI_KIND);
??????? int targetSize = wantMini
??????????????? ? TARGET_SIZE_MINI_THUMBNAIL
??????????????? : TARGET_SIZE_MICRO_THUMBNAIL;
??????? int maxPixels = wantMini
??????????????? ? MAX_NUM_PIXELS_THUMBNAIL
??????????????? : MAX_NUM_PIXELS_MICRO_THUMBNAIL;
??????? SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap();
??????? Bitmap bitmap = null;
??????? MediaFileType fileType = MediaFile.getFileType(filePath);
??????? if (fileType != null && fileType.fileType == MediaFile.FILE_TYPE_JPEG) {
??????????? createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap);
??????????? bitmap = sizedThumbnailBitmap.mBitmap;
??????? }
??????? if (bitmap == null) {
??????????? try {
??????????????? FileDescriptor fd = new FileInputStream(filePath).getFD();
??????????????? BitmapFactory.Options options = new BitmapFactory.Options();
??????????????? options.inSampleSize = 1;
??????????????? options.inJustDecodeBounds = true;
??????????????? BitmapFactory.decodeFileDescriptor(fd, null, options);
??????????????? if (options.mCancel || options.outWidth == -1
??????????????????????? || options.outHeight == -1) {
??????????????????? return null;
??????????????? }
??????????????? options.inSampleSize = computeSampleSize(
??????????????????????? options, targetSize, maxPixels);
??????????????? options.inJustDecodeBounds = false;
??????????????? options.inDither = false;
??????????????? options.inPreferredConfig = Bitmap.Config.ARGB_8888;
??????????????? bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
??????????? } catch (IOException ex) {
??????????????? Log.e(TAG, "", ex);
??????????? }
??????? }
??????? if (kind == Images.Thumbnails.MICRO_KIND) {
??????????? // now we make it a "square thumbnail" for MICRO_KIND thumbnail
??????????? bitmap = extractThumbnail(bitmap,
??????????????????? TARGET_SIZE_MICRO_THUMBNAIL,
??????????????????? TARGET_SIZE_MICRO_THUMBNAIL, OPTIONS_RECYCLE_INPUT);
??????? }
??????? return bitmap;
??? }
??? /**
???? * Create a video thumbnail for a video. May return null if the video is
???? * corrupt or the format is not supported.
???? *
???? * @param filePath the path of video file
???? * @param kind could be MINI_KIND or MICRO_KIND
???? */
??? public static Bitmap createVideoThumbnail(String filePath, int kind) {
??????? Bitmap bitmap = null;
??????? MediaMetadataRetriever retriever = new MediaMetadataRetriever();
??????? try {
??????????? retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
??????????? retriever.setDataSource(filePath);
??????????? bitmap = retriever.captureFrame();
??????? } catch (IllegalArgumentException ex) {
??????????? // Assume this is a corrupt video file
??????? } catch (RuntimeException ex) {
??????????? // Assume this is a corrupt video file.
??????? } finally {
??????????? try {
??????????????? retriever.release();
??????????? } catch (RuntimeException ex) {
??????????????? // Ignore failures while cleaning up.
??????????? }
??????? }
??????? if (kind == Images.Thumbnails.MICRO_KIND && bitmap != null) {
??????????? bitmap = extractThumbnail(bitmap,
??????????????????? TARGET_SIZE_MICRO_THUMBNAIL,
??????????????????? TARGET_SIZE_MICRO_THUMBNAIL,
??????????????????? OPTIONS_RECYCLE_INPUT);
??????? }
??????? return bitmap;
??? }
??? /**
???? * Creates a centered bitmap of the desired size.
???? *
???? * @param source original bitmap source
???? * @param width targeted width
???? * @param height targeted height
???? */
??? public static Bitmap extractThumbnail(
??????????? Bitmap source, int width, int height) {
??????? return extractThumbnail(source, width, height, OPTIONS_NONE);
??? }
??? /**
???? * Creates a centered bitmap of the desired size.
???? *
???? * @param source original bitmap source
???? * @param width targeted width
???? * @param height targeted height
???? * @param options options used during thumbnail extraction
???? */
??? public static Bitmap extractThumbnail(
??????????? Bitmap source, int width, int height, int options) {
??????? if (source == null) {
??????????? return null;
??????? }
??????? float scale;
??????? if (source.getWidth() < source.getHeight()) {
??????????? scale = width / (float) source.getWidth();
??????? } else {
??????????? scale = height / (float) source.getHeight();
??????? }
??????? Matrix matrix = new Matrix();
??????? matrix.setScale(scale, scale);
??????? Bitmap thumbnail = transform(matrix, source, width, height,
??????????????? OPTIONS_SCALE_UP | options);
??????? return thumbnail;
??? }
??? /*
???? * Compute the sample size as a function of minSideLength
???? * and maxNumOfPixels.
???? * minSideLength is used to specify that minimal width or height of a
???? * bitmap.
???? * maxNumOfPixels is used to specify the maximal size in pixels that is
???? * tolerable in terms of memory usage.
???? *
???? * The function returns a sample size based on the constraints.
???? * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
???? * which indicates no care of the corresponding constraint.
???? * The functions prefers returning a sample size that
???? * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
???? *
???? * Also, the function rounds up the sample size to a power of 2 or multiple
???? * of 8 because BitmapFactory only honors sample size this way.
???? * For example, BitmapFactory downsamples an image by 2 even though the
???? * request is 3. So we round up the sample size to avoid OOM.
???? */
??? private static int computeSampleSize(BitmapFactory.Options options,
??????????? int minSideLength, int maxNumOfPixels) {
??????? int initialSize = computeInitialSampleSize(options, minSideLength,
??????????????? maxNumOfPixels);
??????? int roundedSize;
??????? if (initialSize <= 8 ) {
??????????? roundedSize = 1;
??????????? while (roundedSize < initialSize) {
??????????????? roundedSize <<= 1;
??????????? }
??????? } else {
??????????? roundedSize = (initialSize + 7) / 8 * 8;
??????? }
??????? return roundedSize;
??? }
??? private static int computeInitialSampleSize(BitmapFactory.Options options,
??????????? int minSideLength, int maxNumOfPixels) {
??????? double w = options.outWidth;
??????? double h = options.outHeight;
??????? int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
??????????????? (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
??????? int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
??????????????? (int) Math.min(Math.floor(w / minSideLength),
??????????????? Math.floor(h / minSideLength));
??????? if (upperBound < lowerBound) {
??????????? // return the larger one when there is no overlapping zone.
??????????? return lowerBound;
??????? }
??????? if ((maxNumOfPixels == UNCONSTRAINED) &&
??????????????? (minSideLength == UNCONSTRAINED)) {
??????????? return 1;
??????? } else if (minSideLength == UNCONSTRAINED) {
??????????? return lowerBound;
??????? } else {
??????????? return upperBound;
??????? }
??? }
??? /**
???? * Make a bitmap from a given Uri, minimal side length, and maximum number of pixels.
???? * The image data will be read from specified pfd if it's not null, otherwise
???? * a new input stream will be created using specified ContentResolver.
???? *
???? * Clients are allowed to pass their own BitmapFactory.Options used for bitmap decoding. A
???? * new BitmapFactory.Options will be created if options is null.
???? */
??? private static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
??????????? Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,
??????????? BitmapFactory.Options options) {
??????????? Bitmap b = null;
??????? try {
??????????? if (pfd == null) pfd = makeInputStream(uri, cr);
??????????? if (pfd == null) return null;
??????????? if (options == null) options = new BitmapFactory.Options();
??????????? FileDescriptor fd = pfd.getFileDescriptor();
??????????? options.inSampleSize = 1;
??????????? options.inJustDecodeBounds = true;
??????????? BitmapFactory.decodeFileDescriptor(fd, null, options);
??????????? if (options.mCancel || options.outWidth == -1
??????????????????? || options.outHeight == -1) {
??????????????? return null;
??????????? }
??????????? options.inSampleSize = computeSampleSize(
??????????????????? options, minSideLength, maxNumOfPixels);
??????????? options.inJustDecodeBounds = false;
??????????? options.inDither = false;
??????????? options.inPreferredConfig = Bitmap.Config.ARGB_8888;
??????????? b = BitmapFactory.decodeFileDescriptor(fd, null, options);
??????? } catch (OutOfMemoryError ex) {
??????????? Log.e(TAG, "Got oom exception ", ex);
??????????? return null;
??????? } finally {
??????????? closeSilently(pfd);
??????? }
??????? return b;
??? }
??? private static void closeSilently(ParcelFileDescriptor c) {
????? if (c == null) return;
????? try {
????????? c.close();
????? } catch (Throwable t) {
????????? // do nothing
????? }
??? }
??? private static ParcelFileDescriptor makeInputStream(
??????????? Uri uri, ContentResolver cr) {
??????? try {
??????????? return cr.openFileDescriptor(uri, "r");
??????? } catch (IOException ex) {
??????????? return null;
??????? }
??? }
??? /**
???? * Transform source Bitmap to targeted width and height.
???? */
??? private static Bitmap transform(Matrix scaler,
??????????? Bitmap source,
??????????? int targetWidth,
??????????? int targetHeight,
??????????? int options) {
??????? boolean scaleUp = (options & OPTIONS_SCALE_UP) != 0;
??????? boolean recycle = (options & OPTIONS_RECYCLE_INPUT) != 0;
??????? int deltaX = source.getWidth() - targetWidth;
??????? int deltaY = source.getHeight() - targetHeight;
??????? if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
??????????? /*
??????????? * In this case the bitmap is smaller, at least in one dimension,
??????????? * than the target.? Transform it by placing as much of the image
??????????? * as possible into the target and leaving the top/bottom or
??????????? * left/right (or both) black.
??????????? */
??????????? Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
??????????? Bitmap.Config.ARGB_8888);
??????????? Canvas c = new Canvas(b2);
??????????? int deltaXHalf = Math.max(0, deltaX / 2);
??????????? int deltaYHalf = Math.max(0, deltaY / 2);
??????????? Rect src = new Rect(
??????????? deltaXHalf,
??????????? deltaYHalf,
??????????? deltaXHalf + Math.min(targetWidth, source.getWidth()),
??????????? deltaYHalf + Math.min(targetHeight, source.getHeight()));
??????????? int dstX = (targetWidth? - src.width())? / 2;
??????????? int dstY = (targetHeight - src.height()) / 2;
??????????? Rect dst = new Rect(
??????????????????? dstX,
??????????????????? dstY,
??????????????????? targetWidth - dstX,
??????????????????? targetHeight - dstY);
??????????? c.drawBitmap(source, src, dst, null);
??????????? if (recycle) {
??????????????? source.recycle();
??????????? }
??????????? return b2;
??????? }
??????? float bitmapWidthF = source.getWidth();
??????? float bitmapHeightF = source.getHeight();
??????? float bitmapAspect = bitmapWidthF / bitmapHeightF;
??????? float viewAspect?? = (float) targetWidth / targetHeight;
??????? if (bitmapAspect > viewAspect) {
??????????? float scale = targetHeight / bitmapHeightF;
??????????? if (scale < .9F || scale > 1F) {
??????????????? scaler.setScale(scale, scale);
??????????? } else {
??????????????? scaler = null;
??????????? }
??????? } else {
??????????? float scale = targetWidth / bitmapWidthF;
??????????? if (scale < .9F || scale > 1F) {
??????????????? scaler.setScale(scale, scale);
??????????? } else {
??????????????? scaler = null;
??????????? }
??????? }
??????? Bitmap b1;
??????? if (scaler != null) {
??????????? // this is used for minithumb and crop, so we want to filter here.
??????????? b1 = Bitmap.createBitmap(source, 0, 0,
??????????? source.getWidth(), source.getHeight(), scaler, true);
??????? } else {
??????????? b1 = source;
??????? }
??????? if (recycle && b1 != source) {
??????????? source.recycle();
??????? }
??????? int dx1 = Math.max(0, b1.getWidth() - targetWidth);
??????? int dy1 = Math.max(0, b1.getHeight() - targetHeight);
??????? Bitmap b2 = Bitmap.createBitmap(
??????????????? b1,
??????????????? dx1 / 2,
??????????????? dy1 / 2,
??????????????? targetWidth,
??????????????? targetHeight);
??????? if (b2 != b1) {
??????????? if (recycle || b1 != source) {
??????????????? b1.recycle();
??????????? }
??????? }
??????? return b2;
??? }
??? /**
???? * SizedThumbnailBitmap contains the bitmap, which is downsampled either from
???? * the thumbnail in exif or the full image.
???? * mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail
???? * is not null.
???? *
???? * The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight.
???? */
??? private static class SizedThumbnailBitmap {
??????? public byte[] mThumbnailData;
??????? public Bitmap mBitmap;
??????? public int mThumbnailWidth;
??????? public int mThumbnailHeight;
??? }
??? /**
???? * Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image.
???? * The functions returns a SizedThumbnailBitmap,
???? * which contains a downsampled bitmap and the thumbnail data in EXIF if exists.
???? */
??? private static void createThumbnailFromEXIF(String filePath, int targetSize,
??????????? int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) {
??????? if (filePath == null) return;
??????? ExifInterface exif = null;
??????? byte [] thumbData = null;
??????? try {
??????????? exif = new ExifInterface(filePath);
??????????? if (exif != null) {
??????????????? thumbData = exif.getThumbnail();
??????????? }
??????? } catch (IOException ex) {
??????????? Log.w(TAG, ex);
??????? }
??????? BitmapFactory.Options fullOptions = new BitmapFactory.Options();
??????? BitmapFactory.Options exifOptions = new BitmapFactory.Options();
??????? int exifThumbWidth = 0;
??????? int fullThumbWidth = 0;
??????? // Compute exifThumbWidth.
??????? if (thumbData != null) {
??????????? exifOptions.inJustDecodeBounds = true;
??????????? BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions);
??????????? exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels);
??????????? exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize;
??????? }
??????? // Compute fullThumbWidth.
??????? fullOptions.inJustDecodeBounds = true;
??????? BitmapFactory.decodeFile(filePath, fullOptions);
??????? fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels);
??????? fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize;
??????? // Choose the larger thumbnail as the returning sizedThumbBitmap.
??????? if (thumbData != null && exifThumbWidth >= fullThumbWidth) {
??????????? int width = exifOptions.outWidth;
??????????? int height = exifOptions.outHeight;
??????????? exifOptions.inJustDecodeBounds = false;
??????????? sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0,
??????????????????? thumbData.length, exifOptions);
??????????? if (sizedThumbBitmap.mBitmap != null) {
??????????????? sizedThumbBitmap.mThumbnailData = thumbData;
??????????????? sizedThumbBitmap.mThumbnailWidth = width;
??????????????? sizedThumbBitmap.mThumbnailHeight = height;
??????????? }
??????? } else {
??????????? fullOptions.inJustDecodeBounds = false;
??????????? sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions);
??????? }
??? }
}