java - Activity 完成运行后无法在 Android 应用程序中释放内存

标签 java android android-activity memory

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

编辑:

我发现了另一个线程,其中提到它可能只是调试器在内存中持有死 Activity 。我尝试在没有调试器的情况下运行我的应用程序,但在我用完内存之前我仍然只能打开和退出一个记分板 Activity 和两个游戏,因为 Activity 在退出后仍在内存中。我进行了跟踪,调用了 finish()onDestroy()

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

程序流程相当简单,Activity A 是主菜单,它可以启动 Activity B 及以上。除 A 外,其他 Activity 均无法启动新 Activity 。 B 和 up 之间没有通信发生,都是由 A 发起并返回 intents 给 A。

由于项目有点大,我不能真正发布所有代码,但这里是我如何从 A 启动 ScoreboardActivity 并在我的记分板类中从 B 返回 Intent 。 (由于我尝试删除引用, Activity 有点臃肿)

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

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

此处 A 从记分牌接收结果。 A 确实没有充分的理由获得结果,但我添加了这个以查看是否可以在 onResult() 回调中释放内存,所有其他 Activity 都需要返回结果虽然)

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

}

最佳答案

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

现在,如果您想让 GC 在 Activity 上运行,请调用 finish() 会做到这一点。所以,现在控件将转到 onDestroy()。 .

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

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

现在,要从堆栈中删除之前的 Activity ,我将避免重复这些很好的答案, StackOverflow Post_1 StackOverflow Post_2 .

关于java - Activity 完成运行后无法在 Android 应用程序中释放内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36656638/

相关文章:

android - 在 Android Studio 中运行 flutter 项目时出现 Gradle 错误

android - 用另一个 Activity 默认覆盖 onbackPressed()

java - 从 JSON 对象中获取第一个键和值

java - 了解给定代码的代码执行路径

android - Sinch SDK - 如何注销用户?

Java内存分配解决方法

java - Eclipse(可能)不编译外部 jar

android - 当实际调用 onStop 时

java - 从环境变量注入(inject)值的最佳方式是什么?

java - Java JVM 中的信号量处理和线程控制