Thursday, July 7, 2011

java.lang.OutOfMemoryError: bitmap size exceeds VM

Recently while working on an Android app I started running into this error:

 07-07 10:59:14.519: ERROR/AndroidRuntime(20429): FATAL EXCEPTION: main  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429): java.lang.OutOfMemoryError: bitmap size exceeds VM budget  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:470)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:284)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:309)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.graphics.drawable.Drawable.createFromPath(Drawable.java:800)    
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.app.ActivityThread.access$1500(ActivityThread.java:117)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.os.Handler.dispatchMessage(Handler.java:99)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.os.Looper.loop(Looper.java:130)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at android.app.ActivityThread.main(ActivityThread.java:3683)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at java.lang.reflect.Method.invokeNative(Native Method)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at java.lang.reflect.Method.invoke(Method.java:507)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)  
 07-07 10:59:14.519: ERROR/AndroidRuntime(20429):   at dalvik.system.NativeStart.main(Native Method)  

This error was stemming from the large bitmaps I was switching out between activities. When I monitored the Heap Size for my app in the DDMS I was always well under the 16MB cap when the error occurred. I started manually running the garbage collector and even started scaling down my images to no avail. I found that this issue had been reported (Issue 8488) and promptly declined. In the bug report comments you can see that this issue is highly debated whether this is a bug or working as expected.

The best explanation I could find was that the Dalvik heap is used for your Java memory allocations and the Native heap is used for other things like your bitmaps. The DDMS is only reporting the Dalvik heap size. So when switching between activities and setting a new bitmap to an ImageView source I was inflating the total heap size beyond the cap. I was never nulling out the ImageView source references therefore eventually running out of memory.

I also found a nice solution in the comments for this issue. Here is a "BetterActivity" class that extends Activity. This handles nulling out all of the ImageViews to release the memory and running the garbage collector.

1:  import android.app.Activity;  
2:  import android.view.LayoutInflater;  
3:  import android.view.View;  
4:  import android.view.ViewGroup;  
5:  import android.view.ViewGroup.LayoutParams;  
6:  import android.widget.ImageView;  
7:    
8:  public abstract class BetterActivity extends Activity  
9:  {  
10:        private ViewGroup mContentView = null;  
11:          
12:        @Override  
13:        protected void onResume()  
14:        {  
15:             System.gc();  
16:             super.onResume();  
17:        }  
18:     
19:        @Override  
20:        protected void onPause()  
21:        {  
22:             super.onPause();  
23:             System.gc();  
24:        }  
25:     
26:        @Override  
27:        public void setContentView(int layoutResID)  
28:        {  
29:             ViewGroup mainView = (ViewGroup)LayoutInflater.from(this).inflate(layoutResID, null);  
30:             setContentView(mainView);  
31:        }  
32:     
33:        @Override  
34:        public void setContentView(View view)  
35:        {  
36:             super.setContentView(view);  
37:             mContentView = (ViewGroup)view;  
38:        }  
39:     
40:        @Override  
41:        public void setContentView(View view, LayoutParams params)  
42:        {  
43:             super.setContentView(view, params);  
44:             mContentView = (ViewGroup)view;  
45:        }  
46:     
47:        @Override  
48:        protected void onDestroy()  
49:        {  
50:             super.onDestroy();  
51:               
52:             nullViewDrawablesRecursive(mContentView);  
53:             mContentView = null;  
54:             System.gc();  
55:        }  
56:     
57:        private void nullViewDrawablesRecursive(View view)  
58:        {  
59:             if(view != null)  
60:             {  
61:               try  
62:               {  
63:                    ViewGroup viewGroup = (ViewGroup)view;  
64:                      
65:                    int childCount = viewGroup.getChildCount();  
66:                      
67:                    for(int index = 0; index < childCount; index++)  
68:                    {  
69:                         View child = viewGroup.getChildAt(index);  
70:                      nullViewDrawablesRecursive(child);  
71:                    }  
72:               }  
73:               catch(Exception e){}  
74:            
75:               nullViewDrawable(view);  
76:             }    
77:        }  
78:    
79:        private void nullViewDrawable(View view)  
80:        {  
81:             try  
82:             {  
83:                  view.setBackgroundDrawable(null);  
84:             }  
85:             catch(Exception e){}  
86:               
87:             try  
88:             {  
89:                  ImageView imageView = (ImageView)view;  
90:                  imageView.setImageDrawable(null);  
91:                  imageView.setBackgroundDrawable(null);  
92:             }  
93:             catch(Exception e){}  
94:        }  
95:  }  
96: