Out of Memory Error ImageView issue
Solution 1
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:
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
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:
@Override
public void onDestroy() {
super.onDestroy();
imageView.setImageDrawable(null);
}
Solution 2
For those using the Glide image 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:
-
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 really insist 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 still occurring, 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.
Solution 3
Use
((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();
Before change to new image!!
Solution 4
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.
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;
}
Solution 5
You can leave it to 3rd party libraries such as Glide
// imageView.setImageResource(imageId);
Glide.with(this) // Activity or Fragment
.load(imageId)
.into(imageView);
Here's how to add it to your 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
![Admin](/assets/logo_square_200-5d0d61d6853298bd2a4fe063103715b4daf2819fc21225efa21dfb93e61952ea.png)
Admin
Updated on April 18, 2020Comments
-
Admin about 4 years
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!
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)
-
c0dehunter about 10 yearsGarbage collector might not be fast enough meaning this won't work on every occasion.
-
Ken over 9 yearsI agree that sometimes GC is not fast enough. So if we must use Bitmap many times with exact one size, we can make a BitMapPool to reuse the Bitmap instead of recycle it. If you recycle too much BitMap, it maybe affects to UI performance because the time to recycle is too long.
-
Nikolay Hristov over 8 yearsonDestroy() idea is great! But if you cast BitmapDrawable and do .getBitmap() on ImageView without a drawable it will give you only NullPointerException ;)
-
jj. over 7 yearsWouldn't you want to do this (
setIMageDrawable(null)
) in onDestroyView() ? -
JohnnyLambada about 7 years@Sakiboy you'll need to give more info than that to receive help.
-
Sakiboy about 7 yearsSimply suggesting the usage of a library doesn't solve this problem, because these libraries (
Glide
&Picasso
) can still run intoOOM
exceptions... They aren't silver bullets that are impervious to errors. -
JohnnyLambada about 7 yearsTotally agree. However, both libs will fix fix the problem that the OP was having. There are definitely other solid answers in question that detail how to update the OP's code to avoid the OOM he's having. However using one of the libs mentioned is probably a better choice 95% of the time than rolling your own.
-
Sakiboy about 7 yearsFor people who are still facing these
OutOfMemory
issues, even with the use ofGlide
, see my answer for some tips to help fix it. -
JohnnyLambada about 7 years@sakiboy I read your answer -- great work -- highly recommending reading for those who use a lib and still have problems.
-
Sakiboy about 7 yearsThanks! When we use libraries we definitely forgot that they want/need to be used a certain way and that they aren't silver bullets to our problems. But with that being said, they definitely help a lot!
-
Simon Ninon about 7 yearsNot a good answer!!!!!! Yes, libraries can provide good word supported by the community, but they are not perfect and depending on how you can use it, it can be the same or even worse. A good answer to that question is about how to deal with images in general with Android / mobile apps. "Just use glide" is definitely NOT a good answer.
-
Simon Ninon about 7 yearsawesome work, thanks! I'm using picasso right now, but some tips might be handful.
-
Sakiboy about 7 yearsLook @SimonNinon, we already went over this, if you read the comments...
-
Sakiboy about 7 yearsI don't use
Picasso
it requires too much memory. So I only haveGlide
tips. But some of these tips can be used withPicasso
as well. -
Sakiboy about 7 years@jj, I think
onDestroyView()
is the better place, given the whackyFragment
lifecycle. You'd definitely want to do that if you were using aFragment
s in aViewPager
. -
Desolator about 6 yearsgood tips.. additionally I'd do some manual removal and dispose to some of the components, handlers, listeners, views whenever an activity or fragment is destroyed. It helps a lot from the tests I made.
-
Sakiboy about 6 years@SolidSnake I agree, I do that as well in
onDestroy()
(or whatever pertaining callback). Similar to how you should explicitly clear the image view inonViewRecycled()
. -
Micer almost 5 yearsI have my
ImageView
insideConstraintLayout
and using0dp
for width/height. Does it affect image loading in any way, likewrap_content
you mentioned? -
Sakiboy almost 5 years@Micer, that’s a great question. I’d think that constraint layout is smart enough to measure the size of the imageView based on the constraints (w/ match_constraint set - 0dp). Wrap_content is problematic because the ImageView doesn’t know it’s size until after taking the time calculating it.
-
Micer almost 5 years@Sakiboy yep I think so, size calculations in
ConstraintLayout
are probably done before and ImageView already knows its width+height when loading. Thanks for a quick answer. -
Alston almost 5 yearsHi, sir.
imageView.setImageDrawable(null);
can't help as there are many fragments