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)
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.
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:
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
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.
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.
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With