8

I think it is asked and asked before, but still, there are things I can't quite understand.

I have tried two different approaches:

  1. Keep all the images in memory, when certain limit is started to exceed, start removing them
  2. Let Android fix this with SoftReferences

In 2. it's just cleaning them up sometimes the second I allocate them! And I don't allocate too much - 30-40 images 50x50 pixels.

So I am sticking to one. The question is what is the limit?

  1. Can I get some reliable info from the device of how much exactly bitmap memory do I have left? I have done some research, watch DDMS values, it's just taking up more and more space (if I don't clean up) until it explodes. One moment there are only 200K left, next the system provides 2M more...
  2. I am currently using some heuristic decision, based on device model, or screen size. I think this is dead-end in the long run. Got memory exceptions on some phones, completely free on other.
  3. Is there a third solution, the right one?
Danail
  • 10,138
  • 12
  • 51
  • 74
  • 30-40 images sized 50x50 should not cause OOME. Perhaps you load bigger ones? If so, you can scale them down. Also, if some images get cleaned from the SoftReference, they are not being used, so just reload them if necessary. – AlikElzin-kilaka Aug 15 '11 at 15:36

5 Answers5

8

Most important question: Are you recycle()-ing your bitmaps? This is very important for pre-Gingerbread applications (maybe post-Gingerbread too).

Watching the Memory Management for Android Apps session from Google I/O 2011 helped me get a better understanding of the peculiarities of developing for Android.

That video mentions a tool called MAT - a Memory Analyzer - which is useful for determining if you have objects hanging around in memory that have been leaked. It's possibly you have more than the 30-40 you think you have.

For viewing and/or logging the current size of heap, etc., I'd suggest using the code in this answer about Android Out of Memory exceptions.

Community
  • 1
  • 1
dustmachine
  • 9,903
  • 5
  • 23
  • 28
  • Thank you for the link. I have tried to recycle them, but they are used in some views, and that causes crashes when I get back to the activity they are used in. – Danail Aug 17 '11 at 12:06
4

Image caching was a significant part of an app which I've built and put into the app store. The app needs to download images and cache them both in memory and on the SDCard so that their scope extends beyond a single run.

The general idea was to add the images to a Caching Manager which both a) stores the image in an associative container (HashMap), via a key based on the meta-data, and b) writes the image file to the SDCard.

During low memory conditions, I release the HashMap. Yet images can still be retrieved from the SD_Card and once again cache into memory.

I was able to do this without recycling and still see no memory issues. As I understand it, recycling is not necessary but helps to get an earlier release of memory used for "Bitmaps" due to the fact that allocation for Bitmaps, in pre-Gingerbread OSes use Native Memory. i.e. Memory that's not part of the Dalvik heap. So the Garbage Collector doesn't free this memory, rather it's freed by implementation specific policies.

This is from the Cache_Manager class:

public static synchronized void addImage(Bitmap b, String urlString, boolean bSaveToFile, IMAGE_TYPES eIT, boolean bForce)
{
    String szKey = getKeyFromUrlString(urlString, eIT);

    if (false == m_hmCachedImages.containsKey(szKey) || bForce)
    {
        m_hmCachedImages.put(szKey, b);
        if (bSaveToFile)
        {
            boolean bIsNull = false;
            // Write a null object to disk to prevent future query for non-existent image.
            if (null == b)
            {
                try
                {
                    bIsNull = true;
                    b = getNullArt();
                }
                catch (NullPointerException e)
                {
                    e.printStackTrace();
                    throw e;
                }
            }

            // Don't force null art to disk
            if (false == File_Manager.imageExists(szKey) || (bForce && bIsNull == false))
                File_Manager.writeImage(b, szKey);
        }
    }
}

// Here an example of writeImage() from the File_Manager class

public static void writeImage(Bitmap bmp, String szFileName)
{
    checkStorage();

    if (false == mExternalStorageWriteable)
    {
        Log.e("FileMan", "No Writable External Device Available");
        return;
    }

    try
    {
        // Create dirctory if doesn't exist
        String szFilePath = getFilesPath();
        boolean exists = (new File(szFilePath)).exists();
        if (!exists)
        {
            new File(szFilePath).mkdirs();
        }

        // Create file
        File file = new File(szFilePath, szFileName);

        // Write to file
        FileOutputStream os = new FileOutputStream(file);
        bmp.compress(Bitmap.CompressFormat.PNG, 90, os);
    } catch (IOException e)
    {
        // Unable to create file, likely because
        // external storage is
        // not currently mounted.
        Log.e("FileMan", "Error writing file", e);
    } catch (Exception e)
    {
        e.printStackTrace();
        throw e;
    }
}
paiego
  • 3,270
  • 28
  • 37
  • the situation with me is almost the same, the thing is it maybe providing low memory conditions. There is a chance of course that I have memory leaks. – Danail Aug 17 '11 at 12:05
2

The limit is related to the VM heap size on the device it's running on, because this is different from device by device and OS to OS it can range from 16MB (total for the app) to 256MB+ (on tablets).

You will need to either keep it under the lower end, create different builds per device, or have the app check at runtime what the allowance is and load your images accordingly.

There are methods for checking the amount of free space in the heap and it's size:

This API ref will help you with the avilable methods:

http://developer.android.com/reference/android/app/ActivityManager.html#getMemoryClass

Hamid
  • 4,200
  • 10
  • 40
  • 71
  • Hmm. What am I supposed to use there? MemoryInfo? It looks somewhat restrained to me and is it about the native heap? Or the normal heap? I have used so far Debug.getNativeHeapFreeSize() but it is unreliable - it gives the TEMPORARY max limit size, which can be raised more if the app wants more bitmap memory. – Danail Aug 01 '11 at 14:34
  • You don't want to use the native heap info unless you are explicitly allocating natively using the NDK. The Java VM is gives a heap size, some of that heap size can be allocated natively by certain factory calls, such as the bitmap ones. This is deducted from your allocated VM Heap size, even though it's native, so if the VM Heap is 16, you may allocate 4MB natively, you will then have the remaining 12 in the VM Heap. getMemoryClass() and getLargeMemoryClass() will give you the VM heap "class" allowed by the device you are running on. THis is the max your app will be allowed to allocate. – Hamid Aug 01 '11 at 23:48
0

From a high-level perspective, image loading and caching is part of GreenDroid framework. Check it out.

Romain Piel
  • 10,535
  • 15
  • 67
  • 105
-2

You could use a WebView to display images, it has caching built-in. Plus, it supports 2-finger zoom and scroll, without any special code.

NoBugs
  • 8,418
  • 10
  • 72
  • 132