Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animation Drawable causing OutOfMemoryError on second run in Android

I am currently developing a game and I'm trying to use one AnimationDrawable to show one animation in my Main Menu. When I run the game once, it works perfectly. But if i tap the back button, when I open the game again it gives me OutOfMemorryError. If I hit home and go back to the game, it loads but doesn't show the animation, it's empty where it should be.

I think when it opens the game again it tries to load the animation, but it's already loaded from the previous run, can i free that memory somehow? How can I treat that exception?

Searching around I can find that its a common problem in Android, but I couldn't find anything useful.

If it's relevant, my images are 310x316 and I have 114 frames to load, the animation is loaded in a xml.

My MainMenu class:

public class MainMenuActivity extends Activity{

 private String TAG = MainMenuActivity.class.getSimpleName();
 ImageView rabbitMenu ;
 AnimationDrawable ad;   

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);               
        setContentView(R.layout.mainmenu);          
        rabbitMenu = (ImageView) findViewById(R.id.rabbitMenu);
        rabbitMenu.setImageResource(R.drawable.rabbit_menu_animation);
        ad = (AnimationDrawable) rabbitMenu.getDrawable();

    }

public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);       
        ad.start();     

}
}

The error log:

01-31 16:13:11.320: E/AndroidRuntime(19550): FATAL EXCEPTION: main
01-31 16:13:11.320: E/AndroidRuntime(19550): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-31 16:13:11.320: E/AndroidRuntime(19550):    at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
01-31 16:13:11.320: E/AndroidRuntime(19550):    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:563)
01-31 16:13:11.320: E/AndroidRuntime(19550):    at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:439)

Edit:

After @OleGG suggestion and now the second run goes fine, but on the third run I'm apparently trying to use a recycled bitmap.

So I gave up of using AnimationDrawable, and I used AsncTask to solve my problem. Now i keep changing the background of the ImageView on my onProgressUpdate(). May not be the best approach, but now I'm not having any problems! Here is my AsyncTask code in case someone has the same issue!

private class DrawRabbit extends AsyncTask<Void, Integer, Void> {
         private boolean running = true;
         private int fps = 24;
         private int frame = 10014;
         private String drawableName; 
         @Override
         protected Void doInBackground(Void... params) {


             while (running){
                 try {  
                    if (frame == 10014)
                        Thread.sleep(1000);
                    else 
                        Thread.sleep(fps);
                    if (frame < 10160) { 
                        frame++;
                        publishProgress(frame);
                    }
                    else 
                        running = false;

                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
             }               
             return null;
         }

         protected void onProgressUpdate(Integer... frame) {
             drawableName = "rm" + frame[0];
             int res_id = getResources().getIdentifier(drawableName, "drawable", getPackageName());          
             rabbitFrame.setBackgroundResource(res_id);
         }   
     }
like image 284
caiocpricci2 Avatar asked Jan 31 '12 15:01

caiocpricci2


5 Answers

You need to free memory used by bitmaps, when you're exiting this screen. Unfortunately, for earlier versions of Android (afaik this was "fixed" since 3.0, but I can be wrong) memory for bitmaps is allocated through native code and, what is sad, memory should be freed explicitly.

So, I suppose you to try this approach:

  1. Move code for AnimationDrawable to onResume() method.
  2. Add following code for releasing memory to onPause().

    ad.stop();
    for (int i = 0; i < ad.getNumberOfFrames(); ++i){
        Drawable frame = ad.getFrame(i);
        if (frame instanceof BitmapDrawable) {
            ((BitmapDrawable)frame).getBitmap().recycle();
        }
        frame.setCallback(null);
    }
    ad.setCallback(null);
    

I hope it'll help you.

like image 135
OleGG Avatar answered Nov 13 '22 01:11

OleGG


So, I manage to solve my problem with this approach:

Created one AsyncTask for drawing

private class DrawRabbit extends AsyncTask<Void, Integer, Void> {    
     private int fps = 24;
     private int frame = 10014;
     private String drawableName; 
     @Override
       protected Void doInBackground(Void... params) {
         while (running){
           try {  
            if (frame == 10014)
              Thread.sleep(1000);
            else 
              Thread.sleep(fps);
          if (frame < 10160) { 
            frame++;
            publishProgress(frame);
          }
          else 
            running = false;

        } catch (InterruptedException e) { 
          e.printStackTrace();
        }
         }           
         return null;
       }

       protected void onProgressUpdate(Integer... frame) {
         drawableName = "rm" + frame[0];
         int res_id = getResources().getIdentifier(drawableName, "drawable", getPackageName());        
           rabbitFrame.setBackgroundResource(res_id);
       }   
   }

and start it onWindowsFocusChanged

public void onWindowFocusChanged(boolean hasFocus) {    
    super.onWindowFocusChanged(hasFocus);         
    if (hasFocus)
      new DrawRabbit().execute((Void)null) ;
    else 
      running = false;
    Log.d(TAG,"onFocusChanged");
  }

Hope it helps someone!

like image 39
caiocpricci2 Avatar answered Nov 13 '22 02:11

caiocpricci2


You can try by reducing the size if images. My case it worked.

int[] frames = {R.drawable.frame1, R.drawable.frame2, R.drawable.frame3};
AnimationDrawable drawable = new AnimationDrawable();
    for (int fr : frames) {
        Bitmap sample = BitmapFactory.decodeResource(mActivity.getResources(), fr);
        sample = Bitmap.createScaledBitmap(sample, sample.getWidth()/4, sample.getHeight()/4, false);
        drawable.addFrame(new BitmapDrawable(mActivity.getResources(), sample), 30);
    }
like image 45
sujith Avatar answered Nov 13 '22 01:11

sujith


just add "android:largeHeap="true"" in application tag android manifest! :)

like image 45
Matin Ashtiani Avatar answered Nov 13 '22 03:11

Matin Ashtiani


add these two lines in your android manifest file android:hardwareAccelerated="true" and android:largeHeap="true"

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xxx.yyy" >
    <application
        android:allowBackup="false"
        android:fullBackupContent="false"
        android:hardwareAccelerated="true"
        android:largeHeap="true">
</application>

Hope that will help you.

like image 28
Hitesh Dhamshaniya Avatar answered Nov 13 '22 03:11

Hitesh Dhamshaniya