When I minimize my Android App for about 4 or 5 times, I always get the following error:
02-01 19:24:11.980: E/dalvikvm-heap(22362): Out of memory on a 3686416-byte allocation.
02-01 19:24:12.000: E/dalvikvm(22362): Out of memory: Heap Size=62755KB, Allocated=55237KB, Limit=65536KB
02-01 19:24:12.000: E/dalvikvm(22362): Extra info: Footprint=62435KB, Allowed Footprint=62755KB, Trimmed=2144KB
02-01 19:24:12.000: E/Bitmap_JNI(22362): Create Bitmap Failed.
02-01 19:24:12.000: E/Bitmap_JNI(22362): Failed to create SkBitmap!
02-01 19:24:12.000: E/AndroidRuntime(22362): FATAL EXCEPTION: main
02-01 19:24:12.000: E/AndroidRuntime(22362): java.lang.OutOfMemoryError: (Heap Size=62755KB, Allocated=55237KB)
02-01 19:24:12.000: E/AndroidRuntime(22362): at android.graphics.Bitmap.nativeCreateScaledBitmap(Native Method)
02-01 19:24:12.000: E/AndroidRuntime(22362): at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:744)
02-01 19:24:12.000: E/AndroidRuntime(22362): at de.vauge.mb.Utils.getResizedBitmap(Utils.java:56)
02-01 19:24:12.000: E/AndroidRuntime(22362): at de.vauge.mb.MenuView.initialize(MenuView.java:74)
02-01 19:24:12.000: E/AndroidRuntime(22362): at de.vauge.mb.MenuView$1.handleMessage(MenuView.java:137)
02-01 19:24:12.000: E/AndroidRuntime(22362): at android.os.Handler.dispatchMessage(Handler.java:99)
02-01 19:24:12.000: E/AndroidRuntime(22362): at android.os.Looper.loop(Looper.java:156)
02-01 19:24:12.000: E/AndroidRuntime(22362): at android.app.ActivityThread.main(ActivityThread.java:5045)
02-01 19:24:12.000: E/AndroidRuntime(22362): at java.lang.reflect.Method.invokeNative(Native Method)
02-01 19:24:12.000: E/AndroidRuntime(22362): at java.lang.reflect.Method.invoke(Method.java:511)
02-01 19:24:12.000: E/AndroidRuntime(22362): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
02-01 19:24:12.000: E/AndroidRuntime(22362): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
02-01 19:24:12.000: E/AndroidRuntime(22362): at dalvik.system.NativeStart.main(Native Method)
My App consists of only one Activity which has 7 different, self-written Views (all of them containing some Bitmaps) in it, and they are toggled invisible when they are not needed (Probably not good style, but it worked for me until now...). Every one of those Views has a destroy()-function that recycles all the Bitmaps that are used in it, and the onDestroy() of the MainActivity calls all those destroy()-functions. Furthermore, I did not use any static Bitmaps.
So, is there anything else I could try in addition to recycling all Bitmaps and not using static Bitmaps?
Well. Bitmaps on Android can be a little tricky. Can you give better information about the sources of the bitmaps and their sizes?
Otherwise, I'd recommend looking into these things:
If you're loading remote images, check out fresco. You can also check out Picasso. I personally used to like ImageLoader, but it is not being maintained anymore.
If you're using the inPurgable flag that used to be a recommended option, try to find a way around it as it actually causes more memory to be allocated for each image.
If you decode small, local assets frequently, consider saving your drawables in a hashmap and reusing them when needed. Less GC.
If you care to subclass your Application, you can use the OnLowMemory call to know when you probably really need to clean up (mostly good for debugging, not real life situations) ... If that's not too late... :)
Take a look at Chris Banes' blog. This is a pretty interesting memory cache solution
Implement a memory trimmer you call whenever needed and possible.
One other unsurprising optimization is to use smaller objects when you can... Think of your minimal data models and image sizes and try to have a conforming API for those.
For #3 from Ben Max comment I maked two useful classes:
public abstract class SoftReferenceStorage<K, V>{
private static HashMap<Object, SoftReference<Object>> objectsHash = new HashMap<Object, SoftReference<Object>>();
@SuppressWarnings("unchecked")
public V get(K key) {
if (objectsHash.containsKey(key)) {
SoftReference<Object> ref = objectsHash.get(key);
if (ref.get() == null) {
objectsHash.put(key, new SoftReference<Object>(createValueForKey(key)));
return (V)objectsHash.get(key).get();
} else {
return (V)ref.get();
}
} else {
objectsHash.put(key, new SoftReference<Object>(createValueForKey(key)));
return (V)objectsHash.get(key).get();
}
}
protected abstract V createValueForKey(K key);
}
and
public class FrequentlyUsedBitmapResources extends SoftReferenceStorage<Integer, Bitmap>{
private static FrequentlyUsedBitmapResources instance = null;
private Resources resources;
public FrequentlyUsedBitmapResources(Resources resources) {
super();
this.resources = resources;
}
public static FrequentlyUsedBitmapResources getInstance() {
if (instance == null) {
instance = new FrequentlyUsedBitmapResources(HiDriveApp.getContext().getResources());
}
return instance;
}
@Override
protected Bitmap createValueForKey(Integer resId) {
return BitmapFactory.decodeResource(resources, resId);
}
}
can be used like:
Bitmap b = FrequentlyUsedBitmapResources.getInstance().get(R.drawable.overview_photo_placeholder);
Make sure you're loading them in onCreate()
and not in onStart()
or onResume()
. It sounds like they're being reloaded every time you resume, but they aren't being destroyed because onDestroy()
isn't called when you minimize the app.
If the images are on the local device (i.e. either incorporated with your code or come from the user image library), then I may choose NOT to have them simply go invisible or not, but rather just bring them in on the fly from disk. As it turns out, all of these devices are essentially flash based which is extremely quick in comparison to spindle disks. Most likely, the user wont be able to feel the performance hit of disk IO for the images.
this way also, you limit the AMOUNT of images your are holding in memory at any one time.
I agree you should look into Tim' assessment also.