Android 加载大图片OOM异常解决

3年前

Android BitmapFactory的OutOfMemoryError: bitmap size exceeds VM budget解决方案: 一是牺牲图片质量,加载小图片, 二是增加加载内存

摘要:

Android BitmapFactory的OutOfMemoryError: bitmap size exceeds VM budget解决方案:

一是牺牲图片质量,加载小图片,

二是增加加载内存



Android BitmapFactory的OutOfMemoryError: bitmap size exceeds VM budget解决方案:


方案一:


使用BitmapFactory解码一张图片生成Bitmap,有时会遇到该错误,即:java.lang.OutOfMemoryError: bitmap size exceeds VM budget。这往往是由于图片过大造成的。


那么首先想到的思路就是我把图片压缩小一点,这样就会用较小的内存空间来存储图片,载入时将图片缩小,这种方式以牺牲图片质量为代价。在BitmapFactory有一个内部类 BitmapFactory.Options ,当options.inSampleSize>1时,就会以2的指数倍进行缩放,由于每张图片的缩放比例都不一样,那么如何确定这个缩放值呢?


Google官方提供了一种计算图片缩放值的算法,如下:


public 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 == -1) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == -1) ? 128 :
(int) Math.min(Math.floor(w / minSideLength),
Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
return lowerBound;
}
if ((maxNumOfPixels == -1) &&
(minSideLength == -1)) {
return 1;
} else if (minSideLength == -1) {
return lowerBound;
} else {
return upperBound;
}
}


获取缩放值之后,我们看一下BitmapFactory.Options中提供了另一个成员inJustDecodeBounds。当设置inJustDecodeBounds值为true后,decodeFile并不分配空间,但可计算出原始图片的长度和宽度,即opts.width和opts.height。有了这两个参数,再通过上述的算法,即可得到一个恰当的inSampleSize。在图片加载时,只要引用上述方法即可,示例代码如下:


BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imageFile, opts);
opts.inSampleSize = computeSampleSize(opts, -1, 128*128);


//这里一定要将其设置回false,因为之前我们将其设置成了true


opts.inJustDecodeBounds = false;
try {
Bitmap bmp = BitmapFactory.decodeFile(imageFile, opts);
imageView.setImageBitmap(bmp);
} catch (OutOfMemoryError err){}


到此, BitmapFactory.decodeFile会报出上面的OOM 异常了了。同时此算法还可以对图片进行缩放处理。但正如前面提到的,这种方式在一定程度上是以牺牲图片质量为代价的。如何才能更加优化的实现需求?


方案二: 在加载图片时,可以创建一些临时空间,将载入的资源载入到临时空间中。


实现算法如下:


/**
* 根据路径,获取图片bitmap(解决OOM异常)
* @param path 图片路径
* @return
* @throws IOException
*/
public static Bitmap getImageBitmap(String path) throws IOException{
File file = new File(path);
//优化文件读取
RandomAccessFile in = new RandomAccessFile(file, "rws");
final FileChannel channel = in.getChannel();
final int fileSize = (int)channel.size();
final byte[] testBytes = new byte[fileSize];
final ByteBuffer buff = ByteBuffer.allocate(fileSize);
final byte[] buffArray = buff.array();
final int buffBase = buff.arrayOffset();
channel.position(0);
channel.read(buff);
buff.flip();
buff.get(testBytes);
Bitmap bmp = Bitmap_process(buffArray);
return bmp;
}

private static Bitmap Bitmap_process(byte[] buffArray){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither=false;
options.inPurgeable=true;
options.inInputShareable=true;
//关键部分,加载时分配一个内存空间,防止OOM
options.inTempStorage=new byte[32 * 1024]; options.inSampleSize=1;
Bitmap imageBitmap = BitmapFactory.decodeByteArray(buffArray, 0, buffArray.length, options);
return imageBitmap;
}


这样在程序中遇到要解码图片资源时,直接调用getImageBitmap即可得到该解码文件。

另:图片处理完成后即时进行内存回收 调用bm.recycle(),这样将图片占有的内存资源释放。



COMMENTS

需要 后方可回复
如果没有账号可以 一个帐号。