如何在Android中加载大图像并避免内存不足错误?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21392972/
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
How to load large images in Android and avoiding the out of memory error?
提问by dcp3450
I'm working on an app that uses large images (1390?×?870 : 150kb - 50kb). I'm adding images as I tap a trigger/ImageView.
我正在开发一个使用大图像 (1390?×?870 : 150kb - 50kb) 的应用程序。我在点击触发器/ImageView 时添加图像。
At a certain point I'm getting an out of memory error:
在某个时候,我遇到了内存不足错误:
java.lang.OutOfMemoryError
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:613)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:378)
To resize the image I'm doing this:
要调整图像大小,我正在这样做:
Bitmap productIndex = null;
final String imageLoc = IMAGE_LOCATION;
InputStream imageStream;
try {
imageStream = new FileInputStream(imageLoc);
productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
productIV.setImageBitmap(productIndex);
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, String resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(resId, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 3;
final int halfWidth = width / 3;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
I got this way of resizing to save space from the Android Docs: Loading Large Bitmaps Efficiently
我从 Android Docs: Loading Large Bitmaps Efficiently 中得到了这种调整大小以节省空间的方法
According to the log this like is the culprit in the decodeSampledBitmapFromResource
method :
根据日志,这就是该decodeSampledBitmapFromResource
方法的罪魁祸首:
return BitmapFactory.decodeFile(resId, options);
----- edit ----- Here is how I'm adding each item to the FrameLayout.
----- 编辑 ----- 这是我将每个项目添加到 FrameLayout 的方式。
for(int ps=0;ps<productSplit.size();ps++){
//split each product by the equals sign
List<String> productItem = Arrays.asList(productSplit.get(ps).split("="));
String tempCarID = productItem.get(0);
tempCarID = tempCarID.replace(" ", "");
if(String.valueOf(carID).equals(tempCarID)){
ImageView productIV = new ImageView(Configurator.this);
LayoutParams productParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
productIV.setId(Integer.parseInt(partIdsList.get(x)));
productIV.setLayoutParams(productParams);
final String imageLoc = productItem.get(2);
InputStream imageStream;
try {
imageStream = new FileInputStream(imageLoc);
productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
productIV.setImageBitmap(productIndex);
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
productLayers.addView(productIV);
}
}
回答by Bj?rn Hallstr?m
You can use another bitmap-config to heavily decrease the size of the images. The default is RGB-config ARGB8888 which means four 8-bit channels are used (red, green, blue, alhpa). Alpha is transparency of the bitmap. This occupy a lot of memory - imagesize X 4. So if the imagesize is 4 megapixel 16 megabytes will immidiately be allocated on the heap - quickly exhausting the memory.
您可以使用另一个位图配置来大幅减小图像的大小。默认为 RGB-config ARGB8888,这意味着使用四个 8 位通道(红色、绿色、蓝色、alhpa)。Alpha 是位图的透明度。这会占用大量内存 - imagesize X 4。因此,如果 imagesize 为 4 兆像素,则 16 兆字节将立即分配到堆上 - 很快就会耗尽内存。
Instead - use RGB_565 which to some extent deteriorate the quality - but to compensate this you can dither the images.
相反 - 使用 RGB_565 这在某种程度上会降低质量 - 但为了弥补这一点,您可以抖动图像。
So - to your method decodeSampledBitmapFromResource - add the following snippets:
所以 - 在你的方法 decodeSampledBitmapFromResource 中 - 添加以下片段:
options.inPreferredConfig = Config.RGB_565;
options.inDither = true;
In your code:
在您的代码中:
public static Bitmap decodeSampledBitmapFromResource(Resources res, String resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
options.inPreferredConfig = Config.RGB_565;
options.inDither = true;
return BitmapFactory.decodeFile(resId, options);
}
References:
参考:
http://developer.android.com/reference/android/graphics/Bitmap.Config.html#ARGB_8888
http://developer.android.com/reference/android/graphics/Bitmap.Config.html#ARGB_8888
回答by serge
High resolution devices such as S4 usually run out of memory if you do not have your image in the proper folder which is drawable-xxhdpi
. You can also put your image into drawable-nodpi
. The reason it would run out of memorey if your image just in drawable
that the android would scale the image thinking that the image was designed for low resolution.
如果您的图像没有放在正确的文件夹中,例如 S4,则通常会耗尽内存drawable-xxhdpi
。您也可以将图像放入drawable-nodpi
. 如果您的图像只是drawable
因为 android 会缩放图像,认为图像是为低分辨率设计的,那么它会耗尽内存的原因。
回答by Rohit Arya
You can use this beautiful library https://github.com/davemorrissey/subsampling-scale-image-view
您可以使用这个漂亮的库https://github.com/davemorrissey/subsampling-scale-image-view
回答by Budius
Here is how I'm adding each item to the FrameLayout
that's the problem, the code keep adding and adding more images, and doesn't matter how well you resize or how much memory the device have, at certain point it WILL run out of memory. That's because every image you add it's keeping in memory.
Here is how I'm adding each item to the FrameLayout
这就是问题所在,代码不断添加和添加更多图像,并且无论您调整大小或设备具有多少内存,在某些时候它都会耗尽内存。那是因为您添加的每个图像都保存在内存中。
For this type of situation what the apps do is to use a ViewGroup that can recycle views. I don't know your layout, but usually is a ListView
, GridView
or a ViewPager
, by recycling views you re-use the layout and can dispose re-load images as necessary.
对于这种情况,应用程序所做的是使用可以回收视图的 ViewGroup。我不知道你的布局,但通常是一个ListView
,GridView
或者ViewPager
,通过回收的观点,你重复使用的布局和可配置重新载入的图片是必要的。
For the specific purpose of loading and resizing images I strongly advise use Picasso library as it is VERY well written, simple to use and stable.
对于加载和调整图像大小的特定目的,我强烈建议使用 Picasso 库,因为它编写得非常好,使用简单且稳定。
回答by Morrison Chang
You are still going to need to manage the bitmap memory as I wouldn't try to allocate a total space more than 3x the size of the screen (if you think about it makes sense for scrolling behavior). If you are overlaying one image on top of another, at some point, you're hitting an Out Of Memory error. You may need to look at capturing the prior screen image as a single background image to make sure you still fit within the available memory. Or when a new image overlaps an existing image only load and render the visible portion. If performance becomes an issue, then you may need to consider OpenGL Textures but the memory allocation problem is still the same.
您仍然需要管理位图内存,因为我不会尝试分配超过屏幕大小 3 倍的总空间(如果您认为这对滚动行为有意义的话)。如果您将一张图片叠加在另一张图片上,在某些时候,您会遇到内存不足错误。您可能需要将先前的屏幕图像捕获为单个背景图像,以确保您仍然适合可用内存。或者当新图像与现有图像重叠时,仅加载和渲染可见部分。如果性能成为问题,那么您可能需要考虑 OpenGL 纹理,但内存分配问题仍然相同。
Do go through all of the Displaying Bitmaps Trainingas it should give you some additional ideas of how to handle display.
一定要完成所有的显示位图培训,因为它应该会给你一些关于如何处理显示的额外想法。
回答by lee
Use Fresco library to load large images will avoid this error. in xml layout
使用 Fresco 库加载大图像将避免此错误。在 xml 布局中
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="1300dp"
android:layout_height="1300dp"
fresco:placeholderImage="@drawable/my_drawable"
/>
and in javacode
并在javacode中
Uri uri = Uri.parse("https://image.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);