Showing posts with label ImageView. Show all posts
Showing posts with label ImageView. Show all posts

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:    

Monday, May 16, 2011

Android - ImageView scaleType Samples

I found it super frustrating when testing out the different scale types for an ImageView. The Graphical Layout in Eclipse currently does not consistently show an accurate representation of an image in an ImageView when changing the scaleType. The only way to know what the image was going to look like was to run the app and then decide if the current scaleType was what I was looking for.

Here is a table showing samples for all scale types for the ImageView.

scaleType fill_parent x fill_parent 200dp x 200dp 200dp x 200dp
with Overlay
matrix



fitXY



fitStart



fitCenter



fitEnd



center



centerCrop



centerInside




The column labeled "fill_parent x fill_parent" represents the following layout:
 <?xml version="1.0" encoding="utf-8"?>  
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="fill_parent"  
   android:layout_height="fill_parent">  
   <ImageView android:layout_width="fill_parent"  
               android:layout_height="fill_parent"  
               android:src="@drawable/eureka"  
               android:scaleType="matrix">  
   </ImageView>  
 </FrameLayout>  

The column labeled "200dp x 200dp" represents the following layout:
 <?xml version="1.0" encoding="utf-8"?>  
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="fill_parent"  
   android:layout_height="fill_parent">  
   <ImageView android:layout_width="200dp"  
               android:layout_height="200dp"  
               android:layout_gravity="center"  
               android:src="@drawable/eureka"  
               android:scaleType="matrix">  
   </ImageView>  
 </FrameLayout>  

Finally, the column labeled "200dp x 200dp with Overlay" represents the last layout with a 200dp by 200dp rectangle centered and overlaying the ImageView.

For more info on scaleType and each type's definition check out ImageView.ScaleType