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);
}
}
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:
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.
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!
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);
}
just add "android:largeHeap="true"" in application tag android manifest! :)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With