BitmapFactory.decodeResource and inexplicable Out of Memory
Asked Answered
S

5

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)

Sinegold answered 18/3, 2016 at 17:26 Comment(9)
if you use android emulator, how much memory you set in the heap?Anopheles
@Khang .NT 4 I am using a real device 48M heap sizedSinegold
"why I get the error though having enough free memory in the heap?" -- there is no single block big enough on the heap for your request. The Dalvik garbage collector is not a compacting garbage collector, meaning heaps get fragmented. ARTs' garbage collector will compact the heap, to make it possible to allocate larger blocks, but only when the app is in the background.Jacquetta
@Jacquetta There is a way to check the segmented status of a heap?Sinegold
Unfortunately, no.Jacquetta
This is the first time that i see an OOM while 49% of the heap is free, im not fully convicedSinegold
which android version is running on the device?Renz
@Renz 4.1.2 with Dalvik VMSinegold
@GPack:Have you considered using weak instead of soft references?Bleacher
A
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.

Aquiline answered 18/3, 2016 at 17:31 Comment(1)
Do you mean that the OOM happens in the device's memory, not in the VM heap?Sinegold
S
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

Stetson answered 2/5, 2016 at 21:9 Comment(6)
read with more attention the question: there is not leaking memory, there is 49% free of the heap.Sinegold
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 :)Stetson
also @CommonsWare's reason is also correct. Just now I read his comment.Stetson
@Sinegold what's the final conclusion for your question?Stetson
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.Sinegold
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. @Sinegold if you find something interesting please share :)Stetson
N
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.

Nasty answered 3/5, 2016 at 12:19 Comment(2)
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.Sinegold
Are you caching bitmaps in some cache? Second: how are you decoding the bitmap? Provide the line of code.Nasty
E
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.

Eventual answered 4/5, 2016 at 15:6 Comment(1)
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)Sinegold
Z
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);
Zebec answered 5/5, 2016 at 7:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.