36

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)
duggu
  • 37,851
  • 12
  • 116
  • 113
Max
  • 515
  • 1
  • 5
  • 6
  • Please post the entire stacktrace associated with the error – slayton Apr 17 '12 at 22:41
  • a) don't use big images, or b) downscale the images at runtime to reduce memory usage. You do show only 150x120 pixel versions if I get ur code right. – zapl Apr 17 '12 at 22:47
  • 3
    Out of memory is a big problem when displaying images. Check this tutorial here: [Displaying Bitmaps Efficiently](http://developer.android.com/training/displaying-bitmaps/index.html) – Void Main Apr 18 '12 at 01:09
  • this works for me: http://stackoverflow.com/questions/11543326/android-outofmemory-problems/33716836#33716836 – Solivan Nov 16 '15 at 05:26
  • i faced the same problem. In my case, the problem causes because of the large size of the image. Thus when i call setImageResource(), the outofmemory error occurs. – oiyio Apr 10 '18 at 09:19

8 Answers8

36

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);
}
user3265443
  • 535
  • 1
  • 8
  • 25
whyoz
  • 5,168
  • 47
  • 53
  • 1
    onDestroy() idea is great! But if you cast BitmapDrawable and do .getBitmap() on ImageView without a drawable it will give you only NullPointerException ;) – Nikolay Hristov Oct 25 '15 at 10:06
  • 1
    Wouldn't you want to do this (`setIMageDrawable(null)`) in onDestroyView() ? – jj. Oct 12 '16 at 20:56
  • @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 `Fragment`s in a `ViewPager`. – Sakiboy Jun 24 '17 at 19:16
  • Hi, sir. `imageView.setImageDrawable(null);` can't help as there are many fragments – Alston Sep 09 '19 at 09:21
30

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.
Sakiboy
  • 7,252
  • 7
  • 52
  • 69
  • 1
    awesome work, thanks! I'm using picasso right now, but some tips might be handful. – Simon Ninon Jun 23 '17 at 17:15
  • 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 Jun 23 '17 at 17:21
  • 1
    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. – Desolator Apr 18 '18 at 07:16
  • 1
    @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()`. – Sakiboy Apr 18 '18 at 13:11
  • 1
    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? – Micer Aug 21 '19 at 10:15
  • 1
    @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. – Sakiboy Aug 21 '19 at 10:24
  • 1
    @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. – Micer Aug 21 '19 at 10:29
27

Use

((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();

Before change to new image!!

Ken
  • 1,303
  • 13
  • 15
  • 1
    Garbage collector might not be fast enough meaning this won't work on every occasion. – c0dehunter Apr 26 '14 at 21:25
  • 1
    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. – Ken Mar 23 '15 at 01:56
6

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;
    }
Ivo Stoyanov
  • 16,256
  • 8
  • 62
  • 65
3

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

Community
  • 1
  • 1
JohnnyLambada
  • 12,700
  • 11
  • 57
  • 61
  • @Sakiboy you'll need to give more info than that to receive help. – JohnnyLambada Apr 27 '17 at 16:14
  • 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. – Sakiboy Apr 27 '17 at 16:47
  • 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. – JohnnyLambada Apr 27 '17 at 20:51
  • 1
    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. – Sakiboy May 01 '17 at 02:27
  • 1
    @sakiboy I read your answer -- great work -- highly recommending reading for those who use a lib and still have problems. – JohnnyLambada May 02 '17 at 19:17
  • 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! – Sakiboy May 02 '17 at 19:18
  • 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 Jun 23 '17 at 17:14
  • Look @SimonNinon, we already went over this, if you read the comments... – Sakiboy Jun 23 '17 at 17:18
2

Google has the right (perfect) answer:

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):

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);
    }

}
user25
  • 2,873
  • 2
  • 30
  • 66
0

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.

  • use Glide to handle all of the caching.
  • To clear more memory, you should manually remove all of the views and set any ImageView bitmap/drawable to null and clear all event handlers and listeners.
  • Set all of the variables you have in your activity or fragment to null.
  • 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.

After clearing all of the stuff I mentioned earlier. You'll notice that memory will go down every time a fragment/activity gets destroyed.

Desolator
  • 22,411
  • 20
  • 73
  • 96
-1

I had the same problem when i was showing Large image in imageview in LANDSCAPE mode. so i got solved using this code

            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);
         }
Pir Fahim Shah
  • 10,505
  • 1
  • 82
  • 81