【问题标题】:Having trouble freeing up memory in android app after Activities are done running活动完成后在 android 应用程序中释放内存时遇到问题
【发布时间】:2016-08-07 23:41:02
【问题描述】:

我最近做了一个游戏。当我接近完成它时,我开始在开始新活动时遇到 OutOfMemory 问题。在 Android Studio 中检查内存监视器后,我注意到我的活动在完成后仍保留在内存中。如何释放更多内存?

编辑:

我发现了另一个线程,其中提到它可能只是调试器在内存中保存了死活动。我尝试在没有调试器的情况下运行我的应用程序,但在内存不足之前我仍然只能打开和退出一项记分牌活动和两场游戏,因为这些活动在退出后仍在内存中。我做了一个跟踪,finish()onDestroy() 被调用了。

目前,根据 AS 中的内存监视器,我的 MainMenu 活动使用大约 90 MB 并分配了 100。当我开始游戏时,它会跳到 165 已使用,然后从游戏返回并开始新游戏显示 230ish 已使用。之后开始更多游戏会使内存使用量保持在 230 左右,但如果我打开记分板,它会崩溃,因为它需要大约 60mb 并且只有 20mb 可用空间并且分配超过 256mb 会使应用程序崩溃。如果我先打开记分牌,然后玩 2 场比赛,也会发生同样的事情。这些数字比我希望的要高,我正在寻找减少每个活动的 ram 使用量的方法,但我现在主要关心的是在活动完成后释放 ram。

程序流程相当简单,Activity A 是主菜单,它可以启动 Activity B 及以上。除了 A 之外的活动不能开始一个新的活动。 B 和 up 之间没有通信,都是由 A 启动并返回 Intent 给 A。

由于项目有点大,我不能真正发布所有代码,但这里是我如何从 A 开始一个 ScoreboardActivity 并在我的记分牌类中从 B 返回意图。 (由于我尝试删除引用,活动有些臃肿)

从 Activity A:如果用户单击 ID 为 3 的按钮,则启动记分板:

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

这里 A 接收来自记分板的结果。 A 确实没有充分的理由得到结果,但我添加了这个以查看是否可以在 onResult() 回调中释放内存,但所有其他活动确实需要返回结果)

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

这是记分牌的类文件:

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

}

【问题讨论】:

    标签: java android android-activity memory


    【解决方案1】:

    当您使用startActivity(new Intent(.....) 开始活动时,会调用onStop()。这意味着您的活动是不可见的,它还没有完成(即没有调用 GC)。

    您可以尝试清除应用程序数据和/或onStop() 函数上的缓存,如果它不用于进一步的活动。

    【讨论】:

      【解决方案2】:

      这里有几件事需要考虑。首先,好好看看一个活动的life-cycle

      现在,如果您想让 GC 在 Activity 上运行,调用 finish() 即可实现此目的。所以,现在控件将转到onDestroy()

      根据文档,该活动被销毁,其所有资源都排队等待垃圾回收,因为对该活动的引用变得不可访问。因此,该 Activity 正在使用的所有内存都将在下一个 GC 周期期间被释放。

      要查看是否确实发生了这种情况,请将其添加到您的活动的 onDestroy() 方法中:Runtime.getRuntime().gc()

      现在,为了从堆栈中删除以前的活动,我将避免重复一些像StackOverflow Post_1StackOverflow Post_2 这样的好答案。

      【讨论】:

      • 关于堆栈的有趣读物。目前,我使用后退按钮退出记分牌。 onBackPressed 函数的 Override 调用到 finish() 和 super.finish() 那里。我认为这会将它们标记为 GC 清除,但内存监视器从不显示为已释放的记分板分配的内存。将 Runtime.getRuntime().gc() 添加到 onDestroy() 似乎也没有清除它。
      • @E.Hall 是的。这是完全有道理的。在onDestroy() 中放置一个调试器中断,看看它是否成功。我会把钱放在“不”上。
      • 您需要添加标志以在启动新意图时清除堆栈。
      • onDestroy() 在 onBackPressed() 之后触发
      • @E.Hall 是的,当然。想想什么时候你想摆脱以前的活动。你希望它只发生在onBackPressed() 上吗?如果没有,您将不得不在想要摆脱活动时执行相同的代码。当您目前在活动中时,按回是“有点”杀死活动的唯一方法。现在,如果您从旧活动启动新活动,您可能想要终止旧活动以恢复内存。因此,在对旧活动的引用上调用 finish() 应该可以解决问题。
      猜你喜欢
      • 2016-05-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-14
      • 1970-01-01
      相关资源
      最近更新 更多