1

I recently made a game. As I was close to being finished with it, I started running into the OutOfMemory issue when starting new activities. After checking the memory monitor in Android Studio I noticed that my activities stay in memory after I am done with them. How can I free up more memory?

EDIT:

I found another thread where it was mentioned that it may just be the debugger holding dead activities in memory. I tried running my app without debugger, but I can still only open and exit one scoreboard activity and two games before I run out of memory because the activities are still in memory after exiting them. I did a trace and finish() and onDestroy() are called.

Currently, according to memory monitor in AS, my MainMenu activity uses about 90 MB and is allocated 100. When I start a game it jumps up to 165 used, then returning from game and starting new game shows 230ish used. Starting more games after that keeps memory usage at 230ish, but if I open a scoreboard it crashes as it needs about 60mb and only has 20mb free and allocating more than 256 mb crashes the app. Same thing happens if I open scoreboard first, then play 2 games. These numbers are higher than I want them to be, and I am looking into ways to reduce ram usage from each activity, but my main concern right now is to free up the ram when an activity is completed.

The programs flow is fairly simple, Activity A is the main menu and it can start activities B and up. With the exception of A the activities can not start a new Activity. There is no communication happening between B and up, all are started by A and returns intents to A.

Since the project is somewhat large, I can't really post all the code, but here's how I start an ScoreboardActivity from A and return the intent from B in my scoreboard class. (Activity is somewhat bloated from all my attempts at removing references)

From Activity A: if user clicks button with ID 3, start scoreboard:

if ( id == 3) {
    scoreboardIntent = new Intent(getApplicationContext(), ScoreBoard.class);
    startActivityForResult(scoreboardIntent, 0);
}

Here A receives result from Scoreboard. There really isn't a good reason for A to get a result, but I added this to see if I could free memory in the onResult() callback, all the other Activities do need to return a result though)

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data.hasExtra("resultpage")) {
        scoreboardIntent = null;
        return;
}

Here's the classfile for the Scoreboard:

public class ScoreBoard extends AppCompatActivity implements AdapterView.OnItemClickListener, View.OnClickListener {
    ScoreAdapter adapter;
    Firebase myFirebase;
    DataSnapshot scoreSnapshot;
    DataSnapshot noviceSnapshot;
    DataSnapshot expertSnapshot;
    String difficulty = "novice";
    ArrayList<Score> scores = new ArrayList<Score>();
    ArrayList<Score> noviceScores = new ArrayList<Score>();
    ArrayList<Score> expertScores = new ArrayList<Score>();
    ListView listView;
    int clicked = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.scoreboard_layout);
        myFirebase = new Firebase(myFirebaseUri);
        getScoresFromFirebase();
        listView = (ListView) findViewById(R.id.scoreboardlist);
        RadioButton b1 = (RadioButton)findViewById(R.id.button1);
        RadioButton b2 = (RadioButton)findViewById(R.id.button2);
        b1.setOnClickListener(this);
        b2.setOnClickListener(this);
        scores = noviceScores;
        listView.setOnItemClickListener(this);
        adapter = null;
        listView.setAdapter(null);
        adapter = new ScoreAdapter(this, R.layout.scoreboard_item, scores);
        listView.setAdapter(adapter);

        adapter.setNotifyOnChange(true);

    }

    private DataSnapshot getScoresFromFirebase() {
        ...
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}

    @Override
    public void onBackPressed() {
        Intent returnIntent = new Intent();
        returnIntent.putExtra("resultpage",0);
        setResult(Activity.RESULT_OK,returnIntent);
        //For the heck of it I decided to null out everything I could
        adapter.clear();
        adapter = null;
        myFirebase = null;
        scoreSnapshot = null;
        noviceSnapshot = null;
        expertSnapshot = null;
        difficulty = null;
        scores = null;
        noviceScores = null;
        expertScores = null;
        listView = null;
        getIntent().setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        finish();
        super.finish();
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == clicked) return;
        clicked = v.getId();
        //Show topscores for easy mode
        if (v.getId() == R.id.button1) {

            scores = noviceScores;
            adapter = null;
            listView.setAdapter(null);
            adapter = new ScoreAdapter(this, R.layout.scoreboard_item, scores);
            listView.setAdapter(adapter);
            Animation anim = AnimationUtils.loadAnimation(
                    this, android.R.anim.slide_in_left
            );
            anim.setDuration(350);
            listView.startAnimation(anim);

        }
        //show topscores for hard mode
        if (v.getId() == R.id.button2) {
            scores = expertScores;
            adapter = null;
            listView.setAdapter(null);
            adapter = new ScoreAdapter(this, R.layout.scoreboard_item, scores);
            listView.setAdapter(adapter);
        }
    }

}
Daniel
  • 2,355
  • 9
  • 23
  • 30
E. Hall
  • 181
  • 2
  • 10

2 Answers2

3

There are a couple of things to consider here. First, take a good look at the life-cycle of an activity.

Now, if you want to make the GC run on an activity, calling finish() would achieve this. So, now the control will go to the onDestroy().

According to the documentation, the activity is destroyed and all its resources are queued for garbage collection, because a reference to this activity becomes inaccessible. So, all memory that was being used by the activity will be freed during next GC cycle.

To see, if that is indeed happening, add this to the onDestroy() method for your activity: Runtime.getRuntime().gc().

Now, to remove the previous activity from the stack, I will refrain from duplicating some great answers like these, StackOverflow Post_1 StackOverflow Post_2.

Community
  • 1
  • 1
Debosmit Ray
  • 5,228
  • 2
  • 27
  • 43
  • Interesting read about the stack. Currently, I exit the Scoreboard by using the back button. The Override on onBackPressed function calls to finish() and super.finish() there. I thought that would flag them for GC clear, but memory monitor never shows the memory allocated for the scoreboard released. Adding Runtime.getRuntime().gc() to the onDestroy() did not seem to clear it either. – E. Hall Apr 15 '16 at 21:23
  • @E.Hall Yep. That makes complete sense. Put a debugger break inside the `onDestroy()` to see if it hits at all. I'd put my money on 'no'. – Debosmit Ray Apr 15 '16 at 21:26
  • You would need to add flags to clear the stacks when you are launching a new intent. – Debosmit Ray Apr 15 '16 at 21:27
  • onDestroy() is triggered after the onBackPressed() – E. Hall Apr 15 '16 at 21:52
  • @E.Hall Yes, of course. Think about when you want to get rid of previous activities. Do you want it to happen only on `onBackPressed()`? If not, you will have to execute the same code for whenever you want to get rid of an activity. Pressing back is 'kinda' the only way to kill an activity when you are presently inside the activity. Now, if you launch a new activity from the old one, you might want to kill the old activity to recover memory. So, calling `finish()` on a reference to the old activity should do the trick. – Debosmit Ray Apr 15 '16 at 21:58
0

When you start activity with startActivity(new Intent(.....) then onStop() is called. It means your activity is just not visible, it's not finished (i.e. no GC is called).

You can try to clear application data and/or cache on onStop() function if it is not to be used in further activities.

Waqas Ahmed Ansari
  • 1,683
  • 15
  • 30