Out of Memory Error ImageView issue

78,725

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 your ImageView. So if you're ImageView 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 different android:scaleType, preferably: fitCenter or centerCrop.

  • Don't use wrap_content in your ImageView, instead use match_parent or specify the width/height explicitly using a size in dp. If you really insist on using wrap_content in your ImageView, at least set a android:maxHeight/android:maxWidth.
  • Turn off animations with: dontAnimate() on your Glide.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 your Glide.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 the Glide 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 clear Glide 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 the OutOfMemory 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

Share:
78,725
Admin
Author by

Admin

Updated on April 18, 2020

Comments

  • Admin
    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
    c0dehunter about 10 years
    Garbage collector might not be fast enough meaning this won't work on every occasion.
  • Ken
    Ken over 9 years
    I 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
    Nikolay Hristov over 8 years
    onDestroy() idea is great! But if you cast BitmapDrawable and do .getBitmap() on ImageView without a drawable it will give you only NullPointerException ;)
  • jj.
    jj. over 7 years
    Wouldn't you want to do this (setIMageDrawable(null)) in onDestroyView() ?
  • JohnnyLambada
    JohnnyLambada about 7 years
    @Sakiboy you'll need to give more info than that to receive help.
  • Sakiboy
    Sakiboy about 7 years
    Simply suggesting the usage of a library doesn't solve this problem, because these libraries (Glide & Picasso) can still run into OOM exceptions... They aren't silver bullets that are impervious to errors.
  • JohnnyLambada
    JohnnyLambada about 7 years
    Totally 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
    Sakiboy about 7 years
    For people who are still facing these OutOfMemory issues, even with the use of Glide, see my answer for some tips to help fix it.
  • JohnnyLambada
    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
    Sakiboy about 7 years
    Thanks! 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
    Simon Ninon about 7 years
    Not 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
    Simon Ninon about 7 years
    awesome work, thanks! I'm using picasso right now, but some tips might be handful.
  • Sakiboy
    Sakiboy about 7 years
    Look @SimonNinon, we already went over this, if you read the comments...
  • Sakiboy
    Sakiboy about 7 years
    I don't use Picasso it requires too much memory. So I only have Glide tips. But some of these tips can be used with Picasso as well.
  • Sakiboy
    Sakiboy about 7 years
    @jj, I think onDestroyView() is the better place, given the whacky Fragment lifecycle. You'd definitely want to do that if you were using a Fragments in a ViewPager.
  • Desolator
    Desolator about 6 years
    good 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
    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 in onViewRecycled().
  • Micer
    Micer almost 5 years
    I have my ImageView inside ConstraintLayout and using 0dp for width/height. Does it affect image loading in any way, like wrap_content you mentioned?
  • Sakiboy
    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
    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
    Alston almost 5 years
    Hi, sir. imageView.setImageDrawable(null); can't help as there are many fragments