Animation Drawable causing OutOfMemoryError on sec

2019-01-14 05:14发布

问题:

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);
         }   
     }

回答1:

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.



回答2:

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!



回答3:

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);
    }


回答4:

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



回答5:

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.