9

I get a strange Out of Memory error decoding a drawable image resource 960x926px jpg, allocating 3555856 byte. The image is placed only in drawable-xxhdpi (3x) and I am using a hdpi (1.5x) device. Two question:

  • why I get the error though having enough free memory in the heap?

  • allocating for a hdpi device should be ((960/2) x (926/2)) x 4 = 888960 bytes (not 3555856)?

Can someone explain me?

NOTE: the question is about why getting an OOM for 3.5MB allocating while having 22.5MB free memory (see the log)

03-18 17:30:15.050 32750-32750/? D/dalvikvm: GC_FOR_ALLOC freed 10809K, 49% free 23735K/46087K, paused 89ms, total 89ms

03-18 17:30:15.050 32750-32750/? I/dalvikvm-heap: Forcing collection of SoftReferences for 3555856-byte allocation

03-18 17:30:15.160 32750-32750/? D/dalvikvm: GC_BEFORE_OOM freed 29K, 49% free 23705K/46087K, paused 103ms, total 103ms

03-18 17:30:15.160 32750-32750/? E/dalvikvm-heap: Out of memory on a 3555856-byte allocation.

03-18 17:30:15.160 32750-32750/? I/dalvikvm: "main" prio=5 tid=1 RUNNABLE

03-18 17:30:15.160 32750-32750/? I/dalvikvm: | group="main" sCount=0 dsCount=0 obj=0x418fc6a0 self=0x4010c008

03-18 17:30:15.160 32750-32750/? I/dalvikvm: | sysTid=32750 nice=1 sched=0/0 cgrp=apps handle=1075251280

03-18 17:30:15.160 32750-32750/? I/dalvikvm: | schedstat=( 0 0 0 ) utm=3807 stm=859 core=0

03-18 17:30:15.160 32750-32750/? I/dalvikvm: at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)

03-18 17:30:15.160 32750-32750/? I/dalvikvm: at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:636)

03-18 17:30:15.160 32750-32750/? I/dalvikvm: at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:484) 03-18 17:30:15.160 32750-32750/? I/dalvikvm: at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:512)

03-18 17:30:15.160 32750-32750/? I/dalvikvm: at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:542)

GPack
  • 2,494
  • 4
  • 19
  • 50

5 Answers5

2

1)IF you don't have a smaller version in the hdpi folder, it will use the closest match. So it will use the xxhdpi if no hdpi or drawable/ version exists.

2)It won't autoscale. It will read in the full size.

3)If this causes an OOM, you probably are using too much memory in general.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127
2

The reason you are getting OOM is because when image is decoded into memory its bitmap takes more size than the image resolution (almost 4 time I am not sure about this value).

Few points to keep in mind while working with images:

  1. Never handle bitmaps on main thread. Do all the decoding in background.
  2. Always consider screen size or size of the view in which you are going to put the image. For example if size of your screen is 360X720 (some random value) then it is not a good idea to decode full resolution image having resolution greater than the required size (as it will load complete bitmap in the main memory). So always do sampling while decoding.

So try to use the following solution:

Step 1 : Find the screen size

Taken from here

Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;

Step 1 : Create an Async Task to decode bitmap

If you are using async task as inner class then consider using public static inner class (to save memory leak issues) and keep the weak reference of the imageview in which the image is to be loaded. Also pass the image resource or file or stream whatever you want to decode to the constructor. In the below code lets assume you want to decode a resource. Also pass width and height calculated in step 1.

public static class BitmapDecodeTask extends AsyncTask<Void, Void, Bitmap> {
    //the reason to use a weak reference is to protect from memory leak issues.
    private WeakReference<Context> mContextReference;
    private WeakReference<ImageView> mImageViewReference;
    private int mResourceId;
    private int mRequiredWidth;
    private int mRequiredHeight;

    public BitmapDecodeTask(Context context, ImageView imageView, int resourceId, int width, int height) {
        this.mContextReference = new WeakReference<>(context);
        this.mImageViewReference = new WeakReference<>(imageView);
        this.mResourceId = resourceId;
        this.mRequiredWidth = width;
        this.mRequiredHeight = height;
    }

    @Override
    protected Bitmap doInBackground(Void... params) {

        Context context = mContextReference.get();

        if(context != null) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(getResources(), mImageResourceId, options);

            //set inSampleSize
            options.inSampleSize = calculateInSampleSize(options);

            //set inJustDecodeBounds = false;
            options.inJustDecodeBounds = false;

            //decode
            return BitmapFactory.decodeResource(getResources(), mImageResourceId, options);
        }

        return null;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        //check if imageview is available or not
        ImageView imageView = mImageViewReference.get();

        if(imageView != null && bitmap != null) {
            imageView.setImageBitmap(bitmap);
        }
    }

    public static int calculateInSampleSize(BitmapFactory.Options options) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > mRequiredHeight || width > mRequiredWidth) {
            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) > mRequiredHeight 
                && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}

References:

Bitmaps

Community
  • 1
  • 1
Ankit Aggarwal
  • 2,941
  • 2
  • 17
  • 26
  • read with more attention the question: there is not leaking memory, there is 49% free of the heap. – GPack May 02 '16 at 21:49
  • That is what I have written in first line of my answer. Whenever you decode an image it is converted into bitmap which is approximately 4 times the dimensions of the image. I read somewhere about it. So I was just trying to tell that do decoding based on required width and height. That is do sampled decoding. While writing solution I tried to put all the best practices also, that's why there is a mention of memory leak and working off the UI thread. I am not saying that your code is causing memory leak. (960*926)*4 is approximately equal to 3555840. Hope that answers your question :) – Ankit Aggarwal May 03 '16 at 04:55
  • also @CommonsWare's reason is also correct. Just now I read his comment. – Ankit Aggarwal May 03 '16 at 04:58
  • @GPack what's the final conclusion for your question? – Ankit Aggarwal May 05 '16 at 13:15
  • Sorry, i cant assign the bounty because in my opinion all the replies, even if they are very interesting, dont point to the theme of my question: that was why I get the OOM while having enough free memory. Only the comment of CommonsWare is centered on the question, and partially the reply of GabeSechen about autoscale. – GPack May 05 '16 at 13:28
  • ok np :). This is actually a very common problem and I think everyone working with image must have got this error at some stage. I will be happy to know the exact reason and solution. Also as CommonsWare said it can be because of memory fragmentation, but I think system should be handling that. @GPack if you find something interesting please share :) – Ankit Aggarwal May 05 '16 at 13:55
1

The size in memory of the bitmap is width x height x bits per color. I believe you have not used any particular option, su you are using 4 bytes per pixel (Red 1 byte, Green 1 byte, Blue 1 byte, Alpha 1 byte). So: 960*926*4=3555840 bytes.

About your OOM: what seems not so usual to me is:

Forcing collection of SoftReferences for 3555856-byte allocation

Where are you storing the allocated bitmap? Soft references should be avoided on Android.

Mimmo Grottoli
  • 5,758
  • 2
  • 17
  • 27
  • Yes, I know the x4 bytes rule: the second question is about the framework's dpi-drawable folders management, not about the bitmaps allocation math. Forcing collection of SoftReferenced reachable objects before a possible OOM is the usual, standard, behavior of the VM, as the last chance to free memory. The issue here is that there is the 49% free memory. – GPack May 03 '16 at 13:37
  • Are you caching bitmaps in some cache? Second: how are you decoding the bitmap? Provide the line of code. – Mimmo Grottoli May 03 '16 at 13:44
1

Going by the suggestion from Gabe Sechan, I would recommend using a website like makeappicon, which allows you to scale any image that's too big automatically. It's a useful tool, although Android should be handling those scaling problems for you.

Mimmo Grottoli is right about the ARGB bytes, so there's nothing to worry about there as far as I can tell.

The reason you are most likely getting this error is because of a significant memory leak involved in creating(and not destroying) bitmaps, which I learned the hard way from a steg project a while back.

In order to clean up this memory, you can override the onDestroy() method for your Activity/Fragment, or do it manually whenever necessary.

@Override
 public void onDestroy() {
    yourbitmap.recycle();
    yourbitmap = null;
    super.onDestroy();
 }

Hope his was helpful to you.

Vera Gonzalez
  • 578
  • 2
  • 9
  • There is not leaking, the issue is why getting an OOM for 3.5MB allocating while having 22.5MB free memory (see the log above) – GPack May 04 '16 at 15:31
1

It is better to use libraries like Glide or picasso for all image operations (decoding, resizing, downloading etc.):

Replace path with local folder path or server url

Dependency:

compile 'com.github.bumptech.glide:glide:3.5.2'

Load image using Glide

Glide.with (context).load (path).asBitmap().into(imageView);
PEHLAJ
  • 9,980
  • 9
  • 41
  • 53