3

I am trying to prevent OutOfMemoryError in my android app. I have read many post but I cannot solve it yet.

The app has activities with background so I think this is the main problem. OutOfMemoryError only occurs in some devices (maybe due to VM heap) and I need to be sure that this error won't produce a crash in any device.

I have recently read about MAT (Memory Analytics plugin), and I have executed it during the app runtime, here you can see the result:

dominator_tree

dominator_tree

report

report

In this activity I have a background for each orientation (home, home_land). Both sizes are the same (190kb, jpg). When I created the HPROF file the activity was in landscape orientation and I hadn't ran the portrait orientation before. What conclusion can I extract about this result in order to get my purpose?

I can add more information if it is necessary

EDIT

I tried to use the method of this page in order to avoid OutOfMemoryError too, but I couldn't get it. This was my code:

decodeFromResource class

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class decodeFromResource {

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

    public static Drawable getDecodedDrawableFromResource(Resources res, int resId,
            int reqWidth, int reqHeight){
        return new BitmapDrawable(res, decodeSampledBitmapFromResource(res, resId, reqWidth, reqHeight));
    }
}

onCreate method from the main activity

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.home);

        resources = getResources();
        DisplayMetrics metrics = resources.getDisplayMetrics();

        layoutHome = (LinearLayout) findViewById(R.id.home_layout);
        if (resources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
            layoutHome.setBackgroundDrawable(decodeFromResource
                    .getDecodedDrawableFromResource(resources, R.drawable.home,
                            metrics.widthPixels, metrics.heightPixels));
        } else {
            layoutHome.setBackgroundDrawable(decodeFromResource
                    .getDecodedDrawableFromResource(resources,
                            R.drawable.home_land, metrics.heightPixels,
                            metrics.widthPixels));
        }

I had implemented the "Loading Large Bitmaps Efficiently" method only for the background, because apart from this I have only five small buttons with very small size. Should I also need to implement the method for them? Can you see any errors?

  • Do you get the OOM on startup or when repeatedly switching between portrait and landscape? – HHK Jul 01 '14 at 09:41
  • @Hans Kratz It depends of the device. Sometimes after launching the Home Intent I get the OOM but other times It only occurs when I switch the orientation. –  Jul 01 '14 at 10:03

6 Answers6

2

You are probably loading your jpg files as is, which can easily lead to OutOfMemory even on strong devices.

Keep in mind that an image loaded into memory with no compression and that on most devices a single pixel is represented by 4 memory bytes. A 7 MPixel image, for example, will require mem block of 28 MByte which may bring your app real close to OutOfMemory crash.

The solution is simple: always load a scaled-down image, according to your app's needs.

To do this start by reading your image size:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

The code above will NOT load the actual image file. it will only interrogate it for its dimension.

Once you have the deminsion you can calculate the 'sample size' to be used for loading the scaled image. A sample size of N will result in loading 1/(N*N) of the orig image pixels, e.g. for sample size of 4 only 1/16 of the image pixels will be loaded.

And finally load the scaled down image:

final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);

options.inSampleSize = mySampleSize; //<-----------------------

options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeResource(res, resId, options);

Even when doing a scaled-down load it is a good idea to protect your code with a try {...} catch(OutOfmemory) clause and allow for a graceful handling of load failure.

More details here: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

Gilad Haimov
  • 5,767
  • 2
  • 26
  • 30
  • Thanks for the code @Gilad Haimov but I have tried the same solution, and I didn't get to solve anything. I am going to post the way which I used. Tell me if you can see any error please. Thanks again. –  Jun 28 '14 at 23:17
1

Try using android:largeHeap="true" tag in your AndroidManifest.xml This should make sure that android will handle larger bitmaps.

EDIT

I have to point that this is not ultimate solution to your problem, it will not prevent OutOfMemory exception but they will be less likely to appear. Probably Gilad Haimov posted right solution to this problem

Srokowski Maciej
  • 431
  • 5
  • 15
  • Thanks for your answer Sroka but I think this is a wrong way. It is better to try loading bitmaps efficiently as @Gilad Haimov said. –  Jun 28 '14 at 23:20
  • I see that you take my point :). I have replied to @Gilad Haimov. Take a look and tell me if you also can find any error. –  Jun 28 '14 at 23:22
  • You commented and I edited imdpendently of each other :) I just had to make it clear. But still using this tag should help as you will not have to handle OOM excpetions that often – Srokowski Maciej Jun 28 '14 at 23:25
1
options.inPurgeable = true;
options.inInputShareable = true;

these flags allow the actual bits associated with a Bitmap object to be discarded under memory pressure and transparently re-decoded if it turns out you're still using the Bitmap.

j__m
  • 9,392
  • 1
  • 32
  • 56
  • Thanks for your answer @j__m I have added these two lines below options.inJustDecodeBounds = true; in the decodeSampledBitmapFromResource method but I have run the app in a device with VM heap of 32 and OutOfMemoryError appears again. Have you got any suggestion? –  Jul 05 '14 at 15:08
  • 1
    if you're still getting `OutOfMemoryError` then it's not related to your bitmaps. we are thus at an impasse; there's not enough info here to suspect anything else is wrong. – j__m Jul 05 '14 at 18:17
  • Ok @j__m Can you tell me what do you need to see? I haven't got any problem in post more information. Thanks –  Jul 05 '14 at 18:20
  • 1
    i don't know, but if your app is complex and it only happens on some devices (due to the vm heap size), you may not have any alternatives to using the large heap as sroka suggested. – j__m Jul 05 '14 at 18:23
  • My app is simple. The only problem is this. I am thinking and maybe could be this: I am using xxhdpi folder to store every pictures for all devices, with this the outofmemoryerror only occurs in small devices with small vm heap so Should I create more folder for different devices? What would be the name of these folders? Thanks –  Jul 05 '14 at 18:38
  • 1
    if you have bitmaps that are not being loaded using your `decodeFromResource` code - for example, because you set them in xml - then you should absolutely have multiple copies of those bitmaps in different sizes based on the resolution of the target device. if your `drawable-xxhdpi` bitmap is 144x144, then you should have a `drawable-xhdpi` bitmap at 96x96, a `drawable-hdpi` bitmap at 72x72, and a `drawable-mdpi` bitmap at 48x48. – j__m Jul 05 '14 at 18:42
  • So, do you think that my idea could be possible? Shouldn't I create folders based on the screen size (drawable-large, drawable-xlarge...)? Thanks very much for your help. –  Jul 05 '14 at 18:46
  • 1
    typically your layout elements are sized in `dp` units, and thus the `drawable-***dpi` folders should be used. the `-large`, `-xlarge` etc. folders should not be used at all; if you need something to vary based on screen size (usually layouts, not drawables), then you should use the `-w***dp`, `-h***dp`, `-sw***dp` syntax to more precisely indicate the switchover point between resources. – j__m Jul 05 '14 at 18:50
  • 1
    in summary: the old-style `-large` etc. and the new-style `-w***dp` etc. are based on the physical size of the screen, not the number of pixels a particular graphic will occupy. use the `-***dpi` folders to offer the correct number of pixels based on the screen's resolution. – j__m Jul 05 '14 at 18:53
0

Since bitmaps are decompressed, 190kb in storage doesn't really help, did you simply load up the bitmap with little regard of manipulating the parameters fitting memory paging requirement? About up to screen-resolution image can load straight up with no regard of memory.

0

I think when you used DecodeResource such as Bitmap ,you'd better use GC artificially,if you don't do that ,it maybe OOM.

0

Finally, after I have read many post, my last comment in @j__m answer was correct. The problem was drawable folders.

I found this: https://stackoverflow.com/a/19196749/2528167

"Option #1: Just ship the -xxhdpi drawables and let Android downsample them for you at runtime (downside: will only work on fairly recent devices, where -xxhdpi is known)."

I had all my pictures in a xxhdpi folder in order to let Android downsample them at runtime, but as CommonsWare said this only work on recent devices, so I have filled drawable-**dpi folders and now OutOfMemoryError doesn't appear.

Community
  • 1
  • 1