Android Image caching - how?
I think it is asked and asked before, but still, there are things I can't quite understand.
I have tried two different approaches:
- Keep all the images in memory, when certain limit is started to exceed, start removing them
- 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?
- 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...
- 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.
- Is the开发者_JAVA技巧re a third solution, the right one?
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.
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;
}
}
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
From a high-level perspective, image loading and caching is part of GreenDroid framework. Check it out.
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.
精彩评论