21

My app shows a list of 9 categories and each category displays a Gallery-based coverflow (graciously offered by Neil Davies here) with images of the selected category.

The images are fetched from the Web, each ranging from 300K to 500K in size, and stored in an arrayList of Drawables. This data is bound to the coverflow using a BaseAdapter (code below).
Every time I exit the coverflow and go back to the list of categories, I clear the arrayList (again, code below).

In scenario 1, my arrayList contains 5 Drawables. In this scenario, I can freely browse all the categories and show their images. During my test I cycled through all the categories for 5 times, which seems enough to determine that there is no problem.

In scenario 2, my arrayList contains 10 drawables. In this scenario, I get an OutOfMemoryError exception while going through images inside the 5th or 6th categeory:

07-13 08:38:21.266: ERROR/dalvikvm-heap(2133): 819840-byte external allocation too large for this process.
07-13 08:38:21.266: ERROR/(2133): VM won't let us allocate 819840 bytes
07-13 08:38:21.277: DEBUG/skia(2133): --- decoder->decode returned false
07-13 08:38:21.287: WARN/dalvikvm(2133): threadid=25: thread exiting with uncaught exception (group=0x4001b188)
07-13 08:38:21.296: ERROR/AndroidRuntime(2133): Uncaught handler: thread Thread-64 exiting due to uncaught exception
07-13 08:38:21.308: ERROR/AndroidRuntime(2133): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
07-13 08:38:21.308: ERROR/AndroidRuntime(2133): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133): at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133): at android.graphics.drawable.Drawable.createFromStream(Drawable.java:657)

This doesn't make sense to me. If I am leaking memory I would have expected to crash at some point in scenario 1, but I went through all the categories a substantial number of times and didn't crash. I also used the Memory Analyzer plugin for Eclipse which didn't present any potential culprits.

If the system couldn't handle 10 images, like in scenarion 2, I would have expected to crash in the first category, but I crash only after 5 or 6 categories.

The coverflow's adapter functions:

public int getCount() {
 return DataManager.getInstance().getImageBufferInstance().getImageArraySize(); 
}
public Object getItem(int position) { 
 return DataManager.getInstance().getImagesBuffer().get(position);
}
public long getItemId(int position) {
 return position;
}
public View getView(int position, View convertView, ViewGroup parent) { 
 ImageView i;
 if (convertView == null)
 i = new ImageView(mContext);
 else
 i = (ImageView)convertView;
 Drawable bufferedImage = (Drawable)getItem(position);
 Log.v("getView", "position: " + position);
 i.setImageDrawable(bufferedImage);
 i.setLayoutParams(new CoverFlow.LayoutParams(Utils.getInstance().getScreenWidth() / 2,
 Utils.getInstance().getScreenHeight() / 2));
 i.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 
 try{
 //Make sure we set anti-aliasing otherwise we get jaggies
 BitmapDrawable drawable = (BitmapDrawable) i.getDrawable();
 drawable.setAntiAlias(true);
 }
 catch (Exception e)
 {
 Log.v("getView", "Exception: " + e.toString());
 }
 return i; 
 }

populating the data source upon entry to the category:

for (int i = 0; i < ImageBuffer.getInstance().getImageArraySize(); i++) 
{ 
 String imageUrl = ImageBuffer.getInstance().getImageUrl(i); 
 Log.v("Initial", imageUrl); 
 Drawable fullImage = AsyncImageLoader.getInstance().loadImageByUrl(imageUrl); 
 ImageBuffer.getInstance().getImages().add(i, fullImage); 
}

clearing the data source when exiting the category (in finish()):

for (int i = 0; i < ImageBuffer.getInstance().getImageArraySize(); i++) 
{ 
 if (ImageBuffer.getInstance().images.get(i) != null) 
 { 
 ImageBuffer.getInstance().images.get(i).setCallback(null); 
 ImageBuffer.getInstance().images.set(i, null); 
 } 
}

EDIT:

OK, I applied Mathias' LogHeap function on my coverflow and here are some outputs. Prior to loading the first gallery:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 6.20MB of 6.28MB (0.07MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (0.00MB free)
DEBUG/dalvikvm(5221): GC freed 4558 objects / 638152 bytes in 84ms
DEBUG/dalvikvm(5221): GC freed 17 objects / 808 bytes in 67ms

After entering the first gallery:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 14.90MB of 16.89MB (0.07MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 357 objects / 50080 bytes in 68ms
DEBUG/dalvikvm(5221): GC freed 353 objects / 27312 bytes in 67ms

After existing the first gallery:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 14.83MB of 16.89MB (0.11MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 330 objects / 17920 bytes in 77ms
DEBUG/dalvikvm(5221): GC freed 13 objects / 760 bytes in 67ms

After entering the fifth gallery:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 16.80MB of 23.32MB (0.08MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 842 objects / 99256 bytes in 73ms
DEBUG/dalvikvm(5221): GC freed 306 objects / 24896 bytes in 69ms

After exiting the fifth gallery:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 16.74MB of 23.32MB (0.11MB free) in [com.example.Coverlow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 331 objects / 18184 bytes in 68ms
DEBUG/dalvikvm(5221): GC freed 60 objects / 3128 bytes in 68ms

It seems that more and more memory is allocated when entering a gallery, but very little is released after exiting. Am I not clearing my drawables properly? For each element in my arrayList of drawables I call setCallBack(null) and set the element to null. Is that not enough?
Desperate for any insight.
Thanks

Athira
1,2133 gold badges14 silver badges36 bronze badges
asked Jul 13, 2010 at 14:52
2
  • You don't need to use HTML tags for the code blocks. Instead, use what's provided with SO. Does the formatting very nicely for you. Commented Jul 13, 2010 at 14:56
  • Thank you Jason for editing. For some reason I can't get it to display properly using the provided... EDIT: OK, I get it now. 4 spaces... Commented Jul 13, 2010 at 15:16

4 Answers 4

37

The images are fetched from the Web, each ranging from 300K to 500K in size, and stored in an arrayList of Drawables.

The kb file size of the image you're loading from the web isn't directly relevant. Since they're converted into bitmaps you need to calculate width * height * 4 bytes per image for regular ARGB images. (width and height in px).

The bitmaps consume native heap, which usually doesn't show in a hprof. The hprof should only show you the number of objects, i.e. BitmapDrawables or Bitmaps that are left.

I use this code in my app to output the current used memory used by the app and native heap:

public static void logHeap(Class clazz) {
 Double allocated = new Double(Debug.getNativeHeapAllocatedSize())/new Double((1048576));
 Double available = new Double(Debug.getNativeHeapSize())/1048576.0);
 Double free = new Double(Debug.getNativeHeapFreeSize())/1048576.0);
 DecimalFormat df = new DecimalFormat();
 df.setMaximumFractionDigits(2);
 df.setMinimumFractionDigits(2);
 Log.d(APP, "debug. =================================");
 Log.d(APP, "debug.heap native: allocated " + df.format(allocated) + "MB of " + df.format(available) + "MB (" + df.format(free) + "MB free) in [" + clazz.getName().replaceAll("com.myapp.android.","") + "]");
 Log.d(APP, "debug.memory: allocated: " + df.format(new Double(Runtime.getRuntime().totalMemory()/1048576)) + "MB of " + df.format(new Double(Runtime.getRuntime().maxMemory()/1048576))+ "MB (" + df.format(new Double(Runtime.getRuntime().freeMemory()/1048576)) +"MB free)");
 System.gc();
 System.gc();
 // don't need to add the following lines, it's just an app specific handling in my app 
 if (allocated>=(new Double(Runtime.getRuntime().maxMemory())/new Double((1048576))-MEMORY_BUFFER_LIMIT_FOR_RESTART)) {
 android.os.Process.killProcess(android.os.Process.myPid());
 }
}

which I call when starting or finishing an activity during development.

logHeap(this.getClass());

Here are some informative links - generally there are lots of threads about this topic on here.

Here's also a useful slide by Romain Guy (Android Framework engineer) about soft references, weak references, simple caches, image handling: http://docs.huihoo.com/google/io/2009/Th_0230_TurboChargeYourUI-HowtomakeyourAndroidUIfastandefficient.pdf

answered Jul 13, 2010 at 15:54
Sign up to request clarification or add additional context in comments.

6 Comments

Mathias, I edited the question with some output using your function. what do you make of this? Thanks
After carefully examining the heapLog output I realized that something is not cleared properly. I thought that setCallBack(null) and nulling the element would suffice, but only after applying recycle() the memory cleared. Thanks, Mathias.
Hi Rob, I also had recycle() in mind but not for your case/code since you used BitmapDrawables and not Bitmaps itself. Therefore interested, which objects did you call the recyle() upon in the end?
Hi Mathais. I called BitmapDrawable's getBitmap() to get the bitmap and applied recycle() on it.
This information applies to Pre-Honeycomb android. Bitmaps now appear in your Dalvik heap.
|
4

Here are some advices:

  1. Do you use inSampleSize option? It reduces memory consumption if you scale images. Strange out of memory issue while loading an image to a Bitmap object

  2. You should call Bitmap.recycle() when you don't need images any more. I think it's important in your case. Android: OutofMemoryError: bitmap size exceeds VM budget with no reason I can see

answered Jul 14, 2010 at 13:19

1 Comment

Thanks Fedor. After examining the output of Mathias' logHeap function I realized that something is not getting cleared. I applied recycle() and the memory was freed. With your permission, I will credit Mathias with the answer.
0

The image you are loading in gallery 5 or 6 could be too large to load and it is exceeding the maximum size allow by the VM.

answered Jul 13, 2010 at 14:58

5 Comments

Hi Ryan. Gallery 1 is largest in size and it loads. Also, it is not a specific gallery that crashes. It is the number of galleries I already went through that matters. Btw, each gallery is between 2.8 MB and 4.4 MB.
Its not necessarily the size of the gallery, its the size of the image, unless you are "preloading" all the images.
> "which is usually 16Mb": For devices with lower display resolution (usually older models) it's 16 MB, like G1, for devices like Nexus One, HTC Desire the limit is at 24MB, while for the Samsung Galaxy S, it's even higher at 48 MB already. (max heap per app)
@Ryan - I fetch all the images from the Web, place them in an arrayList of Drawables and only then proceed to load the activity which holds the gallery. Btw, is there a limit on a single image??
you preloading all the images is probably the problem. I tried something like that on one of the apps i was writing and if I preloaded all the images in drawables, that is when I would run in to the out of memory exception.
0

You'd better be well aware that the convertView in parameters list of getView is always null. That is to say, gallery does not reuse the old view inside.

Ivan Ferić
4,76311 gold badges40 silver badges47 bronze badges
answered Feb 6, 2013 at 8:33

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.