Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BitmapFactory.decodeResource and inexplicable Out of Memory

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)

like image 483
GPack Avatar asked Mar 18 '16 17:03

GPack


5 Answers

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.

like image 95
Gabe Sechan Avatar answered Nov 06 '22 14:11

Gabe Sechan


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

like image 43
Ankit Aggarwal Avatar answered Nov 06 '22 14:11

Ankit Aggarwal


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.

like image 1
Mimmo Grottoli Avatar answered Nov 06 '22 13:11

Mimmo Grottoli


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.

like image 1
Vera Gonzalez Avatar answered Nov 06 '22 15:11

Vera Gonzalez


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);
like image 1
PEHLAJ Avatar answered Nov 06 '22 14:11

PEHLAJ