Android 处理大型位图
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2220949/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Handling large Bitmaps
提问by Samuh
I have a bunch of image URLs. I have to download these images and display them in my application one-by-one. I am saving the images in a Collection using SoftReferences
and also on Sdcard to avoid refetches and improve user experience.
我有一堆图片网址。我必须下载这些图像并将它们一一显示在我的应用程序中。我正在使用SoftReferences
Sdcard 和 Sdcard将图像保存在集合中,以避免重新获取并改善用户体验。
The problem is I dont know anything about the size of the bitmaps. And as it turns out, I am getting OutOfMemoryExceptions sporadically, when I am using BitmapFactory.decodeStream(InputStream)
method. So, I chose to downsample the images using BitmapFactory Options(sample size=2). This gave a better output: no OOMs, but this affects the quality of smaller images.
问题是我对位图的大小一无所知。事实证明,当我使用BitmapFactory.decodeStream(InputStream)
方法时,我偶尔会遇到 OutOfMemoryExceptions 。因此,我选择使用 BitmapFactory Options(样本大小=2)对图像进行下采样。这提供了更好的输出:没有 OOM,但这会影响较小图像的质量。
How should I handle such cases? Is there a way to selectively downsample only high resolution images?
我应该如何处理此类情况?有没有办法选择性地仅对高分辨率图像进行下采样?
回答by Samuh
There is an option in BitmapFactory.Options
class (one I overlooked) named inJustDecodeBounds
, javadoc of which reads:
类中有一个选项BitmapFactory.Options
(我忽略了一个选项),名为inJustDecodeBounds
,其中的 javadoc 内容为:
If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.
如果设置为 true,解码器将返回 null(无位图),但 out... 字段仍将设置,允许调用者查询位图而无需为其像素分配内存。
I used it to find out the actual size of the Bitmap and then chose to down sample it using inSampleSize
option. This at least avoids any OOM errors while decoding the file.
我用它来找出位图的实际大小,然后选择使用inSampleSize
选项对其进行下采样。这至少可以避免在解码文件时出现任何 OOM 错误。
Reference:
1. Handling larger Bitmaps
2. How do I get Bitmap info before I decode
参考:
1.处理较大的位图
2.如何在解码前获取位图信息
回答by Ryan Amaral
After a few days struggling to avoid all OutOfMemory errors that I was getting with different devices, I create this:
经过几天努力避免我在不同设备上遇到的所有 OutOfMemory 错误,我创建了这个:
private Bitmap getDownsampledBitmap(Context ctx, Uri uri, int targetWidth, int targetHeight) {
Bitmap bitmap = null;
try {
BitmapFactory.Options outDimens = getBitmapDimensions(uri);
int sampleSize = calculateSampleSize(outDimens.outWidth, outDimens.outHeight, targetWidth, targetHeight);
bitmap = downsampleBitmap(uri, sampleSize);
} catch (Exception e) {
//handle the exception(s)
}
return bitmap;
}
private BitmapFactory.Options getBitmapDimensions(Uri uri) throws FileNotFoundException, IOException {
BitmapFactory.Options outDimens = new BitmapFactory.Options();
outDimens.inJustDecodeBounds = true; // the decoder will return null (no bitmap)
InputStream is= getContentResolver().openInputStream(uri);
// if Options requested only the size will be returned
BitmapFactory.decodeStream(is, null, outDimens);
is.close();
return outDimens;
}
private int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) {
int inSampleSize = 1;
if (height > targetHeight || width > targetWidth) {
// Calculate ratios of height and width to requested height and
// width
final int heightRatio = Math.round((float) height
/ (float) targetHeight);
final int widthRatio = Math.round((float) width / (float) targetWidth);
// Choose the smallest ratio as inSampleSize value, this will
// guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
private Bitmap downsampleBitmap(Uri uri, int sampleSize) throws FileNotFoundException, IOException {
Bitmap resizedBitmap;
BitmapFactory.Options outBitmap = new BitmapFactory.Options();
outBitmap.inJustDecodeBounds = false; // the decoder will return a bitmap
outBitmap.inSampleSize = sampleSize;
InputStream is = getContentResolver().openInputStream(uri);
resizedBitmap = BitmapFactory.decodeStream(is, null, outBitmap);
is.close();
return resizedBitmap;
}
This method works with all devices I tested, but I think the quality can be better using other process that I'm not aware.
这种方法适用于我测试过的所有设备,但我认为使用我不知道的其他过程可以使质量更好。
I hope my code can help other developers in the same situation. I also appreciate if a senior developer can help, giving a suggestion about other process to avoid lose (less) quality in the process.
我希望我的代码可以帮助处于相同情况的其他开发人员。如果高级开发人员可以提供帮助,我也很感激,就其他流程提出建议,以避免在流程中损失(降低)质量。
回答by Greg
What I've done myself is :
我自己所做的是:
- use
inJustDecodeBounds
to get the original size of the image - have a fixed maximum Surface for loading the Bitmap (say 1Mpixels)
- check if the image surface is below the limit, if yes, then load it directly
- if not compute the ideal width and height at which you should load the Bitmap to stay below the max surface (there is some simple maths to do here). This give you a float ratio that you want to apply when loading the bitmap
- now you want to translate this ratio to a suitable
inSampleSize
(a power of 2 that doesn't degrade the image quality). I use this function :
- 用于
inJustDecodeBounds
获取图像的原始大小 - 有一个固定的最大表面来加载位图(比如 1Mpixels)
- 检查图像表面是否低于限制,如果是,则直接加载
- 如果不计算您应该加载位图以保持在最大表面以下的理想宽度和高度(这里有一些简单的数学运算)。这为您提供了加载位图时要应用的浮点比率
- 现在您想将此比率转换为合适的
inSampleSize
(不会降低图像质量的 2 的幂)。我使用这个功能:
int k = Integer.highestOneBit((int)Math.floor(ratio)); if(k==0) return 1; else return k;
- then because the Bitmap will have been loaded with a slightly higher resolution than the max surface (because you had to use a smaller power of 2), you'll have to resize the Bitmap, but it will be much faster.
- 然后因为位图加载的分辨率比最大表面稍高(因为您必须使用较小的 2 次幂),所以您必须调整位图的大小,但速度会快得多。