Android 内存不足错误 ImageView 问题
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10200256/
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
Out of Memory Error ImageView issue
提问by Max
I'm new in Android programming and I got an error that says that my app run out of memory, this exampled I copied from a book and it is working with small pictures resolution, but when I added a few pictures with a bigger resolution out of memory error appears, may be I do something wrong or just don't know all I should yet to work with images, if anyone know what should i change so that this error won't appear again, pleas help. Thank you anticipate!
我是 Android 编程新手,我收到一条错误消息,说我的应用程序内存不足,这个例子是我从书中复制的,它使用的是小图片分辨率,但是当我添加了一些分辨率更大的图片时出现内存错误,可能是我做错了什么,或者只是不知道我应该处理图像的所有内容,如果有人知道我应该更改什么以便不再出现此错误,请帮忙。谢谢期待!
The source code:
源代码:
public class ImageViewsActivity extends Activity {
//the images to display
Integer[] imageIDs={
R.drawable.pic1,
R.drawable.pic2,
R.drawable.pic3,
R.drawable.pic4,
R.drawable.pic5
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final ImageView iv=(ImageView) findViewById(R.id.image1);
Gallery gallery=(Gallery) findViewById(R.id.gallery);
gallery.setAdapter(new ImageAdapter(this));
gallery.setOnItemClickListener(new OnItemClickListener(){
public void onItemClick(AdapterView<?> parent, View v, int position, long id){
Toast.makeText(getBaseContext(), "pic"+(position+1)+" selected", Toast.LENGTH_SHORT).show();
//display the image selected
try{iv.setScaleType(ImageView.ScaleType.FIT_CENTER);
iv.setImageResource(imageIDs[position]);}catch(OutOfMemoryError e){
iv.setImageBitmap(null);
}
}
});
}
public class ImageAdapter extends BaseAdapter{
private Context context;
private int itemBackground;
public ImageAdapter(Context c){
context=c;
//setting the style
TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
itemBackground = a.getResourceId(R.styleable.Gallery1_android_galleryItemBackground, 0);
a.recycle();
}
//returns the number of images
public int getCount() {
// TODO Auto-generated method stub
return imageIDs.length;
}
//returns the ID of an item
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
//returns the ID of an item
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
//returns an ImageView view
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ImageView iv= new ImageView(context);
iv.setImageResource(imageIDs[position]);
iv.setScaleType(ImageView.ScaleType.FIT_XY);
iv.setLayoutParams(new Gallery.LayoutParams(150,120));
iv.setBackgroundResource(itemBackground);
return iv;
}
}}
ERROR HERE:
错误在这里:
04-18 10:38:31.661: D/dalvikvm(10152): Debugger has detached; object registry had 442 entries
04-18 10:38:31.661: D/AndroidRuntime(10152): Shutting down VM
04-18 10:38:31.661: W/dalvikvm(10152): threadid=1: thread exiting with uncaught exception (group=0x4001d820)
04-18 10:38:31.691: E/AndroidRuntime(10152): FATAL EXCEPTION: main
04-18 10:38:31.691: E/AndroidRuntime(10152): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.graphics.Bitmap.nativeCreate(Native Method)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.graphics.Bitmap.createBitmap(Bitmap.java:499)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.graphics.Bitmap.createBitmap(Bitmap.java:466)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:371)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:539)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:508)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:365)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:728)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.content.res.Resources.loadDrawable(Resources.java:1740)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.content.res.Resources.getDrawable(Resources.java:612)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.widget.ImageView.resolveUri(ImageView.java:520)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.widget.ImageView.setImageResource(ImageView.java:305)
04-18 10:38:31.691: E/AndroidRuntime(10152): at image.view.GalleryView$ImageAdapter.getView(GalleryView.java:95)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.widget.Gallery.makeAndAddView(Gallery.java:776)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.widget.Gallery.fillToGalleryLeft(Gallery.java:695)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.widget.Gallery.trackMotionScroll(Gallery.java:406)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.widget.Gallery$FlingRunnable.run(Gallery.java:1397)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.os.Handler.handleCallback(Handler.java:618)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.os.Handler.dispatchMessage(Handler.java:123)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.os.Looper.loop(Looper.java:154)
04-18 10:38:31.691: E/AndroidRuntime(10152): at android.app.ActivityThread.main(ActivityThread.java:4668)
04-18 10:38:31.691: E/AndroidRuntime(10152): at java.lang.reflect.Method.invokeNative(Native Method)
04-18 10:38:31.691: E/AndroidRuntime(10152): at java.lang.reflect.Method.invoke(Method.java:552)
04-18 10:38:31.691: E/AndroidRuntime(10152): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:917)
04-18 10:38:31.691: E/AndroidRuntime(10152): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:674)
04-18 10:38:31.691: E/AndroidRuntime(10152): at dalvik.system.NativeStart.main(Native Method)
回答by whyoz
To add on Ken's answer, which is a solid piece of code, I thought I'd knock it down after he set it up:
要添加 Ken 的答案,这是一段可靠的代码,我想我会在他设置后将其删除:
if(imageView != null) {
((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();
}
imageView = (ImageView) view.findViewById(R.id.imageView);
imageView.setImageResource(resID);
NOTE: This won't work if you are trying to swap an image you already recycled. You'll get something like this in LOGCAT
注意:如果您尝试交换已经回收的图像,这将不起作用。你会在 LOGCAT 中得到这样的东西
Canvas: trying to use a recycled bitmap
画布:尝试使用回收位图
So what I do now if I don't have to load a bunch of different images asynchronously, I simply put this in onDestroy when dealing with fragments and large background images:
因此,如果我不必异步加载一堆不同的图像,我现在要做的是,在处理片段和大背景图像时,我只需将其放在 onDestroy 中:
@Override
public void onDestroy() {
super.onDestroy();
imageView.setImageDrawable(null);
}
回答by Ken
Use
用
((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();
Before change to new image!!
在更改为新图像之前!!
回答by Sakiboy
For those using the Glideimage loading library, who are still running into these OutOfMemory Exception
issues, there're many things you can do to make Glide
use less memory and hopefully fix your problem. Here are a few of them:
对于那些使用Glide图像加载库的人来说,他们仍然OutOfMemory Exception
遇到这些问题,你可以做很多事情来Glide
减少内存的使用,并希望能解决你的问题。这里有几个:
Don't use
android:scaleType="fitXY"
inside of yourImageView
. So if you'reImageView
looks like this:<ImageView android:id="@android:id/icon" android:layout_width="@dimen/width" android:layout_height="@dimen/height" android:adjustViewBounds="true" android:scaleType="fitXY" <!-- DON'T USE "fitXY"! --> />
Change the
ImageView
to use a differentandroid:scaleType
, preferably:fitCenter
orcenterCrop
.- Don't use
wrap_content
in yourImageView
, instead usematch_parent
or specify thewidth
/height
explicitly using a size indp
. If you reallyinsist on usingwrap_content
in yourImageView
, at least set aandroid:maxHeight
/android:maxWidth
. - Turn off animations with:
dontAnimate()
on yourGlide.with()...
request. If you're loading lots of potentially large images (as you would in a list/grid), specify a
thumbnail(float sizeMultiplier)
load in your request. Ex:Glide.with(context) .load(imageUri) .thumbnail(0.5f) .dontAnimate() .into(iconImageView);
Temporarily lower
Glide
's memory footprint during certain phases of your app by using:Glide.get(context).setMemoryCategory(MemoryCategory.LOW)
.- Only cache in memory if you need to, you can turn it off with:
skipMemoryCache(true)
on yourGlide.with()...
request. This will still cache the images to disk, which you'll probably want since you're foregoing the in-memory cache. - If you're loading a
Drawable
from your local resources, make sure that the image you're trying to load ISN'T SUPER HUGE. There are plenty of image compression tools available online. These tools will shrink the sizes of your images while also maintaining their appearance quality. - If loading from local resources use
.diskCacheStrategy(DiskCacheStrategy.NONE)
. Hook into the
onTrimMemory(int level)
callback that Android provides to trim theGlide
cache as needed. Ex.@Override public void onTrimMemory(int level) { super.onTrimMemory(level); Glide.get(this).trimMemory(level); }
If displaying images in a
RecyclerView
you can explicitly clearGlide
when views are recycled, like so:@Override public void onViewRecycled(MyAdapter.MyViewHolder holder) { super.onViewRecycled(holder); Glide.clear(holder.imageView); }
- If this is stilloccurring, even after you've "tried everything", the problem might be your application(GASP!), and
Glide
is just the one thing that's pushing it to theOutOfMemory Exception
zone... So be sure you don't have any memory leaks in your application.Android Studio
provides tools for identifying memory consumption issues in you app. - Lastly check the issue page on Glide's GitHub, for similar issues that may provide insight into fixing your problem(s). The repo is managed really well and they're very helpful.
不要
android:scaleType="fitXY"
在你的ImageView
. 所以如果你ImageView
看起来像这样:<ImageView android:id="@android:id/icon" android:layout_width="@dimen/width" android:layout_height="@dimen/height" android:adjustViewBounds="true" android:scaleType="fitXY" <!-- DON'T USE "fitXY"! --> />
更改
ImageView
以使用不同的android:scaleType
,最好是:fitCenter
或centerCrop
。- 不要
wrap_content
在您的 中使用ImageView
,而是使用match_parent
或显式指定width
/height
中的大小dp
。如果你真的坚持wrap_content
在你的. 中使用ImageView
,至少设置一个android:maxHeight
/android:maxWidth
。 - 关闭动画:
dontAnimate()
根据您的Glide.with()...
要求。 如果您正在加载大量可能较大的图像(就像在列表/网格中那样),请
thumbnail(float sizeMultiplier)
在您的请求中指定一个负载。前任:Glide.with(context) .load(imageUri) .thumbnail(0.5f) .dontAnimate() .into(iconImageView);
Glide
在应用程序的某些阶段,使用以下方法暂时降低的内存占用:Glide.get(context).setMemoryCategory(MemoryCategory.LOW)
。- 如果需要,仅缓存在内存中,您可以
skipMemoryCache(true)
根据您的Glide.with()...
要求关闭它:这仍然会将图像缓存到磁盘,因为您放弃了内存缓存,所以您可能会想要。 - 如果您
Drawable
从本地资源加载 a ,请确保您尝试加载的图像不是超大。网上有很多图像压缩工具。这些工具将缩小图像的尺寸,同时保持其外观质量。 - 如果从本地资源加载,请使用
.diskCacheStrategy(DiskCacheStrategy.NONE)
. 连接到
onTrimMemory(int level)
Android 提供的回调以Glide
根据需要修剪缓存。前任。@Override public void onTrimMemory(int level) { super.onTrimMemory(level); Glide.get(this).trimMemory(level); }
如果在 a 中显示图像,
RecyclerView
您可以明确清除Glide
何时回收视图,如下所示:@Override public void onViewRecycled(MyAdapter.MyViewHolder holder) { super.onViewRecycled(holder); Glide.clear(holder.imageView); }
- 如果这种情况仍然发生,即使在您“尝试了一切”之后,问题也可能出在您的应用程序(GASP!)上,并且
Glide
只是将其推到OutOfMemory Exception
区域的一件事......所以请确保您没有应用程序中的任何内存泄漏。Android Studio
提供用于识别应用中内存消耗问题的工具。 - 最后查看Glide 的 GitHub 上的问题页面,了解类似的问题,这些问题可能有助于深入了解您的问题。回购管理得非常好,他们非常有帮助。
回答by Ivo Stoyanov
Images come in all shapes and sizes. In many cases they are larger than required for a typical application user interface (UI). For example, the system Gallery application displays photos taken using your Android devices's camera which are typically much higher resolution than the screen density of your device.
Given that you are working with limited memory, ideally you only want to load a lower resolution version in memory. The lower resolution version should match the size of the UI component that displays it. An image with a higher resolution does not provide any visible benefit, but still takes up precious memory and incurs additional performance overhead due to additional on the fly scaling.
图像有各种形状和大小。在许多情况下,它们比典型应用程序用户界面 (UI) 所需的要大。例如,系统图库应用程序显示使用您的 Android 设备的相机拍摄的照片,这些照片的分辨率通常比您设备的屏幕密度高得多。
鉴于您使用的内存有限,理想情况下您只想在内存中加载较低分辨率的版本。较低分辨率的版本应与显示它的 UI 组件的大小相匹配。具有更高分辨率的图像不会提供任何明显的好处,但仍会占用宝贵的内存并由于额外的动态缩放而导致额外的性能开销。
Source: Loading Large Bitmaps Efficiently
来源:有效加载大型位图
Based on the information above I would recommend you instead of setting the image like this:
根据以上信息,我建议您不要像这样设置图像:
setImageResource(resId);
to set it like this:
像这样设置:
setScaledImage(yourImageView, resId);
and Copy & Paste the methods below:
并复制和粘贴以下方法:
private void setScaledImage(ImageView imageView, final int resId) {
final ImageView iv = imageView;
ViewTreeObserver viewTreeObserver = iv.getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
iv.getViewTreeObserver().removeOnPreDrawListener(this);
int imageViewHeight = iv.getMeasuredHeight();
int imageViewWidth = iv.getMeasuredWidth();
iv.setImageBitmap(
decodeSampledBitmapFromResource(getResources(),
resId, imageViewWidth, imageViewHeight));
return true;
}
});
}
private static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds = true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
private 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 / 2;
final int halfWidth = width / 2;
// 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;
}
回答by JohnnyLambada
You can leave it to 3rd party libraries such as Glide
您可以将其留给Glide等 3rd 方库
// imageView.setImageResource(imageId);
Glide.with(this) // Activity or Fragment
.load(imageId)
.into(imageView);
Here's how to add it to your build.gradle
:
以下是如何将其添加到您的build.gradle
:
compile group: 'com.github.bumptech.glide', name: 'glide', version: '3.7.0'
Square's Picasso does it too Picasso load drawable resources from their URI
Square 的 Picasso 也这样做了Picasso 从他们的 URI 加载可绘制资源
回答by user25
Google has the right (perfect) answer:
谷歌有正确(完美)的答案:
https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
An example how I use it in fragments:
我如何在片段中使用它的示例:
private ImageView mImageView;
private View view;
private int viewWidth;
private int viewHeight;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_episode_list, container, false);
mImageView = (ImageView) view.findViewById(R.id.ImageView);
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
viewWidth = view.getMeasuredWidth();
viewHeight = view.getMeasuredHeight();
mImageView.setImageBitmap(Methods.decodeSampledBitmapFromResource(getResources(),
R.drawable.YourImageName, viewWidth, viewHeight));
}
});
}
return view;
}
I put these Google methods to my "Methods" class (to any other useful methods):
我将这些 Google 方法放在我的“方法”类中(任何其他有用的方法):
public class Methods {
...
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 / 2;
final int halfWidth = width / 2;
// 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;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
}
回答by SolidSnake
Additional notes to @Sakiboy answer. Although I'm probably too late for my answer but here's my solution I found it works without the need to do much of code changes.
@Sakiboy 回答的附加说明。虽然我的答案可能为时已晚,但这是我的解决方案,我发现它无需进行大量代码更改即可工作。
- use
Glide
to handle all of the caching. - To clear more memory, you should manually remove all of the
views
and set anyImageView
bitmap/drawable tonull
and clear all event handlers and listeners. - Set all of the variables you have in your
activity
orfragment
tonull
. - You need to put your logic inside
onDestroy
and you should be good to go. - Optional step is to add
System.gc()
at the end of your code.
- 用于
Glide
处理所有缓存。 - 要清除更多内存,您应该手动删除所有
views
并设置任何ImageView
位图/可绘制对象null
并清除所有事件处理程序和侦听器。 - 将您的
activity
或中的所有变量设置fragment
为null
. - 你需要把你的逻辑放在里面
onDestroy
,你应该很高兴。 - 可选步骤是
System.gc()
在代码末尾添加。
After clearing all of the stuff I mentioned earlier. You'll notice that memory will go down every time a fragment/activity gets destroyed.
在清除我之前提到的所有东西之后。您会注意到每次片段/活动被破坏时内存都会下降。
回答by Pir Fahim Shah
I had the same problem when i was showing Large image in imageview in LANDSCAPE mode. so i got solved using this code
当我在 LANDSCAPE 模式下在 imageview 中显示大图像时,我遇到了同样的问题。所以我用这个代码解决了
File imgFile = new File(imageFile.getAbsolutePath()); // path of your file
FileInputStream fis = null;
try {
fis = new FileInputStream(imgFile);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
options.inPurgeable = true;
options.inScaled = true;
Bitmap bm = BitmapFactory.decodeStream(fis, null,options);
profileIV.setImageBitmap(bm);
}