Bitmap recycle with largeHeap enabled
Asked Answered
F

5

10

Before enabling largeHeap option, I was handling large bitmaps and it's consume almost the entire memory available for the application, and recycling it over navigation and loading new ones works round on almost the full heap available. However when some operations needs a bit more memory the application crashes. So I enabled largeHeap=true to have a bit more memory.

But doing this has a unexpected behavior, it's looks like that recycle() method of bitmaps do not work most of times, and the application that worked in 58Mb of memory (and exceeds sometimes throwing a OutOfMemoryException) now consumes memory exponentially and keeps growing (for now the test I did came to 231Mb allocated memory), the expected behavior is that the memory management keeps working and the application will not use more than 60Mb.

How can I avoid that? Or efficiently recycle bitmaps?

EDIT: Actually, I made it give a OutOfMemoryError when allocating more than 390Mb of memory on the device. Reading GC_* logs shown that only GC_FOR_ALLOC that freed 3.8Mb sometimes, but almost never other GC runs freed something.

Froh answered 3/10, 2012 at 20:42 Comment(2)
Have you looked this great video? youtube.com/watch?v=_CruQY55HOk I assume you have a memory leak in your codeFled
You'll have to tell us whether this is pre-Honeycomb or >= HoneycombDisharoon
W
21

You should probably have a look at Displaying Bitmaps Efficiently which includes several ways to handle large Bitmaps Efficiently,

  • Loading Large Bitmaps Efficiently
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;

This will give you the size of the image before downloading and on that basis you can check the size of your device and scale it using calculateInSampleSize() and decodeSampledBitmapFromResource() given in the explanation of docs.

Calculating how much we need to scale the image,

if (imageHeight > reqHeight || imageWidth > reqWidth) {
      if (imageWidth > imageHeight ) {
          inSampleSize = Math.round((float)imageHeight / (float)reqHeight);
      } else {
          inSampleSize = Math.round((float)imageWidth / (float)reqWidth);
      }
    }
int inSampleSize = Math.min(imageWidth / reqWidth,imageHeight / reqHeight);

The you can set the inSampleSize,

 options.inSampleSize = inSampleSize;

Then finally make sure you call,

options.inJustDecodeBounds = false;

else it will return Bitmap as null

  • Processing Bitmaps Off the UI Thread

    Processing Bitmap on UI thread is never safe so its always better to do that in a background thread and update UI after the process is completed.

  • Caching Bitmaps

    LruCache is available from API 12 but if you are interested it using below versions it is also available in Support Library too. So caching of Images should be done efficiently using that. Also you can use DiskLruCache for images where you want then to remain for longer period in extenal storage.

  • Clearing the Cache

    Sometimes when your image size is too large even caching the image causes OutOfMemoryError so in that case its better to clear the cache when your image is out of the scope or not used for longer period so that other images can be cached.

    I had created a demo example for the same, you can download from here

Whitewood answered 10/10, 2012 at 12:13 Comment(6)
good answer for managing bitmaps but how about efficiently recycle bitmaps?Giulia
why to recycle Bitmap? If a bitmap is recycled it can't be reused and that will lead to unusual downloading of images again and again. So, better is use caching of Bitmaps.Whitewood
The question is really about efficiently recycle bitmaps, because I already implemented following the managing bitmaps article. And even cache does not work for me, cause the images being displayed cannot be collected until it's requested.Froh
I have to say that I get to turn off the largeHeap feature cause now I'm using the ljnigraphics library to native draw (previously I moved some giant ByteBuffers around to draw a image) but the question is still valid. With largeHeap the GC does not collect nothing at all.Froh
Well, this answer is just a resume of Displaying Bitmaps Efficiently from Android dev articles. I'm awarding you the bounty anyway, cause I wont be selfish and answer and award myself.Froh
This is a nice explanation +1.Offish
S
3

Your case behaves as expected. Before Honeycomb, recycle() was unconditionally freeing the memory. But on 3.0 and above, bitmaps are part of normal garbage collected memory. You have plenty of RAM on the device, you allowed the JVM to allocate more than the 58M limit, now the garbage collector is satisfied and has no incentive to reclaim memory occupied by your bitmaps.

You can verify this by running on an emulator with controlled amount of RAM, or load some memory consuming service on your device - GC will jump to work. You can use DDMS to further investigate your memory usage.

You can try some solutions for bitmap memory management: Bitmaps in Android Bitmap memory leaks http://blog.javia.org/how-to-work-around-androids-24-mb-memory-limit/, but start with the official Android bitmap tips, as explained in @Lalit Poptani's detailed answer.

Note that moving the bitmaps to OpenGL memory as textures has some performance implications (but perfect if you will render these bitmaps through OpenGL in the end). Both textures and malloc solutions require that you explicitly free the bitmap memory which you don't use anymore.

Sophistry answered 14/10, 2012 at 6:25 Comment(0)
A
2

Definitely @Lalit Poptani answer is the way to do it, you should really scale your Bitmaps if they are very large. A preferable way is that this is done server-side if this is possible, since you will also reduce NetworkOperation time.

Regarding the implementation of a MemoryCache and DiskCache this again is the best way to do it, but I would still recommend to use an existing library, which does exactly that (Ignition) and you will save yourself a lot of time, and also a lot of memory leaks, since because your Heap does not get emptied after GC I can assume that you probably have some memory leaks too.

Apartheid answered 16/10, 2012 at 14:41 Comment(1)
Well I literally "Search & Destroy" memory leaks, the memory is round over navigation as expected.Froh
A
1

To address your dilemma, I believe this is the expected behaviour.

If you want to free up memory you can occasionally call System.gc(), but really you should for the most part let it manage the garbage collection itself.

What I recommend is that you keep a simple cache (url/filename to bitmap) of some sort which keeps track of its own memory usage by calculating the number of bytes that each Bitmap is taking up.

/**
 * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
 * @param width
 * @param height
 * @param config
 * @return
 */
public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
    long pixels=width*height;
    switch(config){
    case ALPHA_8: // 1 byte per pixel
        return pixels;
    case ARGB_4444: // 2 bytes per pixel, but depreciated
        return pixels*2;
    case ARGB_8888: // 4 bytes per pixel
        return pixels*4;
    case RGB_565: // 2 bytes per pixel
        return pixels*2;
    default:
        return pixels;
    }
}

Then you query how much memory the app is using and how much is available, maybe take half of that and try to keep the total image cache size under that, by simply removing (dereferencing) the older images from your list when your are coming up against this limit, not recycling. Let the garbage collector clean up the bitmaps when they are both derefrrenced from your cache and are not being used by any views.

/**
 * Calculates and adjusts the cache size based on amount of memory available and average file size
 * @return
 */
synchronized private int calculateCacheSize(){
    if(this.cachedBitmaps.size()>0){
        long maxMemory = this.getMaxMemory(); // Total max VM memory minus runtime memory 
        long maxAllocation = (long) (ImageCache.MEMORY_FRACTION*maxMemory);
        long avgSize = this.bitmapCacheAllocated / this.cachedBitmaps.size();
        this.bitmapCacheSize = (int) (maxAllocation/avgSize);
    }
    return this.bitmapCacheSize;
}

I would recommend you stay away from using recycle(), it causes a lot of intermittent exceptions (like when seemingly finalized views try to access recycled bitmaps) and in general seems buggy.

Atthia answered 15/10, 2012 at 2:41 Comment(1)
Well, I already deferred objects, but before I recycled it, I'll have a try without invoking recycle()Froh
D
1

You have to be very careful with handling bitmaps on Android. Let me rephrase that: you have to watch out handling bitmaps even on a system with 4 gigs of RAM. How large are these guys and do you have a lot? You might have to chop and tile it up if it's large. Remember that you use using video RAM, which is a different animal than system RAM.

Pre-Honeycomb, the Bitmaps were allocated on the C++ layer, so that RAM usage was invisible to Java and couldn't be accessed by the garbage collector. A 3 MP uncompressed Bitmap with the RGB24 colorspace uses around 9-10 megabytes (around 2048x1512). So, larger images can easily fill up your heap. Also remember that in whatever is being used for video RAM (sometimes dedicated RAM, sometimes shared with system), the data is usually stored uncompressed.

Basically, if you are targeting pre-Honeycomb devices, you almost have to manage Bitmap object as if you were coding a C++ program. Running the bitmap recycle() onDestory() usually works if there aren't many images, but if you have a ton of images on the screen, you may have to handle them on-the-fly. Also, if you launch another activity, you may have to consider putting in logic into onPause() and onResume().

You can also cache the images using the Android file system or SQLite when they aren't in video RAM. You may be able to get away with caching it in RAM if you are using a format like .jpg or a .png with a lot of repeated data/

Disharoon answered 16/10, 2012 at 12:37 Comment(1)
This is the case, there's tons of images that are paginated trough the application itself. I handle then on-the-fly with recycle and really works fine.Froh

© 2022 - 2024 — McMap. All rights reserved.