Strange Outofmemory Issue While Loading an Image to a Bitmap Object

Strange OutOfMemory issue while loading an image to a Bitmap object

The Android Training class, "Displaying Bitmaps Efficiently", offers some great information for understanding and dealing with the exception `java.lang.OutOfMemoryError: bitmap size exceeds VM budget when loading Bitmaps.



Read Bitmap Dimensions and Type

The BitmapFactory class provides several decoding methods (decodeByteArray(), decodeFile(), decodeResource(), etc.) for creating a Bitmap from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemory exception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Options class. Setting the inJustDecodeBounds property to true while decoding avoids memory allocation, returning null for the bitmap object but setting outWidth, outHeight and outMimeType. This technique allows you to read the dimensions and type of the image data prior to the construction (and memory allocation) of the bitmap.

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;

To avoid java.lang.OutOfMemory exceptions, check the dimensions of a bitmap before decoding it unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory.



Load a scaled-down version into Memory

Now that the image dimensions are known, they can be used to decide if the full image should be loaded into memory or if a subsampled version should be loaded instead. Here are some factors to consider:

  • Estimated memory usage of loading the full image in memory.
  • The amount of memory you are willing to commit to loading this image given any other memory requirements of your application.
  • Dimensions of the target ImageView or UI component that the image is to be loaded into.
  • Screen size and density of the current device.

For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an ImageView.

To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize to true in your BitmapFactory.Options object. For example, an image with resolution 2048x1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration of ARGB_8888). Here’s a method to calculate a sample size value that is a power of two based on a target width and height:

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

Note: A power of two value is calculated because the decoder uses a
final value by rounding down to the nearest power of two, as per the
inSampleSize documentation.

To use this method, first decode with inJustDecodeBounds set to true, pass the options through and then decode again using the new inSampleSizevalue andinJustDecodeBoundsset tofalse`:

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

This method makes it easy to load a bitmap of arbitrarily large size into an ImageView that displays a 100x100 pixel thumbnail, as shown in the following example code:

mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

You can follow a similar process to decode bitmaps from other sources, by substituting the appropriate BitmapFactory.decode* method as needed.

Strange OutOfMemory issue while loading an image to a Bitmap object

The Android Training class, "Displaying Bitmaps Efficiently", offers some great information for understanding and dealing with the exception `java.lang.OutOfMemoryError: bitmap size exceeds VM budget when loading Bitmaps.



Read Bitmap Dimensions and Type

The BitmapFactory class provides several decoding methods (decodeByteArray(), decodeFile(), decodeResource(), etc.) for creating a Bitmap from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemory exception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Options class. Setting the inJustDecodeBounds property to true while decoding avoids memory allocation, returning null for the bitmap object but setting outWidth, outHeight and outMimeType. This technique allows you to read the dimensions and type of the image data prior to the construction (and memory allocation) of the bitmap.

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;

To avoid java.lang.OutOfMemory exceptions, check the dimensions of a bitmap before decoding it unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory.



Load a scaled-down version into Memory

Now that the image dimensions are known, they can be used to decide if the full image should be loaded into memory or if a subsampled version should be loaded instead. Here are some factors to consider:

  • Estimated memory usage of loading the full image in memory.
  • The amount of memory you are willing to commit to loading this image given any other memory requirements of your application.
  • Dimensions of the target ImageView or UI component that the image is to be loaded into.
  • Screen size and density of the current device.

For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an ImageView.

To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize to true in your BitmapFactory.Options object. For example, an image with resolution 2048x1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration of ARGB_8888). Here’s a method to calculate a sample size value that is a power of two based on a target width and height:

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

Note: A power of two value is calculated because the decoder uses a
final value by rounding down to the nearest power of two, as per the
inSampleSize documentation.

To use this method, first decode with inJustDecodeBounds set to true, pass the options through and then decode again using the new inSampleSizevalue andinJustDecodeBoundsset tofalse`:

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

This method makes it easy to load a bitmap of arbitrarily large size into an ImageView that displays a 100x100 pixel thumbnail, as shown in the following example code:

mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

You can follow a similar process to decode bitmaps from other sources, by substituting the appropriate BitmapFactory.decode* method as needed.

OutOfMemory exception when loading bitmap from external storage

Probably nothing wrong with your API usage, I guess all we can do is infer that using the AssetManager involves less behind-the-scenes heap allocation than opening a random file from the SD card.

800KB is a serious allocation in anybody's book... this will doubtless be for the decompressed image pixels. Given that you know the size of the image, what depth is it? If it's 32bpp then try overriding that using inPreferredConfig.

Out of memory issue while loading bitmaps in Android

Two (big) parts to my answer. The first is aimed more directly at your question, and the second part takes a step back as I share how I learned to implement my own solution that is aimed more at people running into this for the first time.

Don't use bitmap.recycle(), because you really shouldn't have to. While this clears the memory being used for that bitmap, you'll probably run into issues with the bitmap still being used somewhere.

You should also use WeakReference everywhere there's a possibility the object will hang onto a bitmap (loading Tasks, ImageViews, etc). From the documentation:

Weak references are useful for mappings that should have their entries removed automatically once they are not referenced any more (from outside). The difference between a SoftReference and a WeakReference is the point of time at which the decision is made to clear and enqueue the reference:
  • A SoftReference should be cleared and enqueued as late as possible, that is, in case the VM is in danger of running out of memory.
  • A WeakReference may be cleared and enqueued as soon as is known to be weakly-referenced.

Both should theoretically work, but we have a little problem: Java finalizers. They aren't guaranteed to run in time, and unfortunately, that's where our little friend Bitmap is clearing it's memory. If the bitmaps in question are created slowly enough, the GC probably has enough time to recognize our SoftReference or WeakReference object and clear it from memory, but in practice that's not the case.

The short of it is that it's extremely easy to outpace the Garbage Collector when working with objects that use finalizers like Bitmaps (I think some IO classes use them too). A WeakReference will help our timing problem a little bit better than a SoftReference. Yeah, it'd be nice if we could hold a bunch of images in memory for insane performance, but many Android devices simply don't have the memory to do this, and I've found that no matter how large the cache is you'll still run into this problem if you aren't clearing references as soon as humanly possible.

As far as your caching goes, the first change I'd make is to ditch your own memory cache class and just use the LruCache that's found in the Android compatibility library. Not that your cache has issues or anything, but it removes another point of headaches, it's already done for you, and you won't have to maintain it.

Otherwise, the biggest problem I see with what you have is that PhotoToLoad is holding a strong reference to an ImageView, but more of this whole class could use some tweaking.

A short but nicely-written blog post explaining a great method for holding references to correct ImageViews while downloading images can be found on Android's blog, Multithreading for Performance. You can also see this sort of practice in-use on Google's I/O app, whose source code is available. I expand on this a little in the second part.

Anyway, instead of trying to map the URLs being loaded to the ImageView it's intended for with a Collection as you're doing, following what's done on the blog post above is an elegant way to reference back to the ImageView in question while avoiding using a recycled ImageView by mistake. And of course, it's a good example of how the ImageViews are all weakly referenced, which means our Garbage Collector is allowed to free up that memory faster.

OK. Now the second part.

Before I continue on the issue more in general and get even more long-winded I'll say that you're on the right track, and that the rest of my answer probably treads on a lot of ground you already covered and know about, but I'm hoping it will also benefit someone newer at this so bear with me.

As you already know, this is a very common problem on Android with a fairly long explanation that's been covered before (shakes fist at finalizers). After banging my own head against the wall for hours on end, trying various implementations of loaders and cachers, watching the "heap growth/cleanup race" in logs endlessly, and profiling memory usage and tracing objects with various implementations until my eyes would bleed, a few things have become clear to me:

  1. If you find yourself trying to tell the GC when to fire, you're going down the wrong path.
  2. You're in for a world of pain if you try to call bitmap.recycle() on bitmaps that are used in the UI, such as ImageViews.
  3. A major reason this is such a headache is because there's way too much misinformation out there on this topic on how to solve the problem. So many of the tutorials or examples out there look good in theory, but in practice are absolute trash (confirmed by all the profiling and tracing I mentioned above). What a maze to navigate!

You have two options. The first is to use a well-known and tested library. The second is to learn the right way to accomplish this task and gain some insightful knowledge along the way. For some libraries you can do both options.

If you look at this question, you'll find a few libraries that will accomplish what you are trying to do. There's also a couple of great answers that point to very useful learning resources.

The route I took myself was the more difficult one, but I'm obsessed with understanding solutions and not simply just using them. If you want to go the same route (it's worth it), you should first follow Google's tutorial "Displaying Bitmaps Efficiently".

If that didn't take, or you want to study a solution used in practice by Google themselves, check out the utility classes that handle bitmap loading and caching in their I/O 2012 app. In particular, study the following classes:

  • DiskLruCache.java
  • ImageCache.java
  • ImageFetcher.java
  • ImageWorker.java
  • public static ImageFetcher getImageFetcher(final FragmentActivity activity) in UIUtils.java

And of course, study some of the Activities to see how they use these classes. Between the official Android tutorial and the I/O 2012 app, I was able to successfully roll my own to fit what I was doing more specifically and know what was actually happening. You can always study some of the libraries I mentioned in the question I linked to above as well to see a couple of slightly different takes.

OutOfMemoryException loading big image to Bitmap object with the Compact Framework

I don't believe the problem is a memory leak. Instead, the problem is a lack of available memory.

Even though the compressed image size is 200kb, when you load it as a bitmap it will be decompressed and stored in memory in native Bitmap format. Given a height and width of 1500px each, and assuming a bitmap format of 32bpp (the default when not specified), you're looking at 9MB of allocated memory

1500 * 1500 * 4 = 9MB.

Given the memory constraints present in the mobile device OS (32MB/process - space allocated by system dlls), you may well be in a memory crunch scenario. It's unknown to me of course what other memory is allocated by the application you are running this code in.

Try the same code on the same device with a smaller image. You should see that it executes fine.



Related Topics



Leave a reply



Submit