设备上的Android内存泄漏,而不是模拟器上

时间:2016-06-10 22:31:19

标签: java android memory-leaks garbage-collection

我正在写一个游戏,以帮助教我的儿子一些语音:这是我第一次尝试编程 Java ,虽然我之前使用过其他语言。游戏有四个活动:一个启动屏幕,在你解雇它之前初始化一系列变量;另一个选择用户;第三个选择要玩的游戏级别;第四个真正玩游戏。

我的问题是,如果您反复进出游戏活动,该活动最终会崩溃 - logcat显示 OOM错误。看着我这样做的堆大小,并查看了 MAT 的堆转储,看起来好像我泄漏了整个第四个活动 - GC 只是没有被触发。

我已经尝试了很多东西来追踪和修复泄漏 - 其中大部分都是,我确定改进(例如,从该活动中删除所有非静态内部类)而不修复问题。但是,我只是尝试在模拟器上运行相同的东西(与我的设备相同的目标和API)并且没有泄漏 - 堆大小上下变化, GC 经常被触发,它不会崩溃。

所以我打算在这里发布活动的代码并寻求帮助,找出可能导致泄漏的原因,但我不再确定这是正确的问题。相反,我想知道为什么它在模拟器上有效,但手机却没有...有没有人有任何想法?

IDE:Android Studio 2.1
目标:Android 6,API 23(最低SDK 8)
模拟器:Android Studio
设备:Sony Xperia Z2(现在正在运行6.0.1,但我在最近更新前遇到了同样的问题,即在API 22上运行)

活动代码:

public class GameActivity extends AppCompatActivity implements TextToSpeech.OnInitListener {

//TTS Object
private static TextToSpeech myTTS;
//TTS status check code
private int MY_DATA_CHECK_CODE = 0;
//LevelChooser request code
public static Context gameContext;
private int level;
public static String user;
private Typeface chinacat;
public static Activity gameActivity = null;
private static int[] goldstars = {R.drawable.goldstar1, R.drawable.goldstar2, R.drawable.goldstar3};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    gameActivity = this;
    gameContext = this;
    level = getIntent().getIntExtra("level", 1);
    user = getIntent().getStringExtra("user");
    chinacat = Typeface.createFromAsset(getAssets(), "fonts/chinrg__.ttf");

    Intent checkTTSIntent = new Intent();
    checkTTSIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
    startActivityForResult(checkTTSIntent, MY_DATA_CHECK_CODE);
}

@Override
public void onStop() {
    if (myTTS != null) {
        myTTS.stop();
    }
    super.onStop();
}

@Override
public void onDestroy() {
    if (myTTS != null) {
        myTTS.shutdown();
    }
    Button ok_button = (Button) findViewById(R.id.button);
    ok_button.setOnClickListener(null);
    ImageView tickImageView = (ImageView) findViewById(R.id.tickImageView);
    tickImageView.setOnClickListener(null);

    super.onDestroy();
}

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == MY_DATA_CHECK_CODE) {
        if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
            myTTS = new TextToSpeech(this, this);
        } else {
            Intent installTTSIntent = new Intent();
            installTTSIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
            startActivity(installTTSIntent);
        }
    }
}

public void onInit(int initStatus) {
    //if tts initialized, load layout and level and assign listeners for layout elements
    if (initStatus == TextToSpeech.SUCCESS) {
        myTTS.setLanguage(Locale.ENGLISH);

        setContentView(R.layout.activity_main);

        ImageView imageView = (ImageView) findViewById(R.id.myImageView);

        PhonemeGroup levelGroup = MainActivity.gamelevel[level]; //set possible words
        levelGroup.setSubset(); //randomize subset of possible words for actual test
        PhonicsWord[] testSet = levelGroup.getSubset(); //fill array of test words

        TextView[] targetView = new TextView[3]; //textviews for beginning, middle & end of word
        targetView[0] = (TextView) findViewById(R.id.targetWord0);
        targetView[1] = (TextView) findViewById(R.id.targetWord1);
        targetView[2] = (TextView) findViewById(R.id.targetWord2);

        TextView[] answersView = new TextView[3]; //textviews for possible user answer choices
        answersView[0] = (TextView) findViewById(R.id.letter0);
        answersView[1] = (TextView) findViewById(R.id.letter1);
        answersView[2] = (TextView) findViewById(R.id.letter2);

        //set first target word, image for word, and possible answers
        testSet[0].setWord(levelGroup, targetView, answersView, imageView);
        testSet[0].speakWord(myTTS);
        //subset index is equal to array index for testSet, but visible to & settable by methods
        levelGroup.setSubsetIndex(0);

        for(int i=0; i<3; i++) {
            answersView[i].setTypeface(chinacat);
        }

        TextView letter0 = (TextView) findViewById(R.id.letter0);
        letter0.setOnClickListener(new LetterOnClickListener(testSet, levelGroup, targetView, answersView, 0) );
        TextView letter1 = (TextView) findViewById(R.id.letter1);
        letter1.setOnClickListener(new LetterOnClickListener(testSet, levelGroup, targetView, answersView, 1) );
        TextView letter2 = (TextView) findViewById(R.id.letter2);
        letter2.setOnClickListener(new LetterOnClickListener(testSet, levelGroup, targetView, answersView, 2) );

        Button ok_button = (Button) findViewById(R.id.button);
        ok_button.setOnClickListener(new OKButtonOnClickListener(testSet, levelGroup, targetView, level) );

        ImageView tickImageView = (ImageView) findViewById(R.id.tickImageView);
        tickImageView.setOnClickListener(new TickClick(myTTS, testSet, levelGroup, targetView, answersView, imageView) );
        imageView.setOnClickListener(new WordImageClick(testSet, levelGroup) );
    }
    /*else if TODO*/
}

private static class WordImageClick implements View.OnClickListener {
    //speaks the test word when the test image is clicked
    PhonicsWord[] testSet;
    PhonemeGroup levelGroup;

    public WordImageClick(PhonicsWord[] testSet, PhonemeGroup levelGroup) {
        this.testSet = testSet;
        this.levelGroup = levelGroup;
    }

    @Override
    public void onClick(View view) {
        testSet[levelGroup.getSubsetIndex()].speakWord(myTTS);
    }
}

private static class LetterOnClickListener implements View.OnClickListener {
    PhonemeGroup levelGroup;
    PhonicsWord currentWord;
    PhonicsWord[] testSet;
    TextView[] targetView;
    TextView[] answersView;
    int item;
    int phonemeclicked;

    public LetterOnClickListener(PhonicsWord[] testSet, PhonemeGroup levelGroup, TextView[] targetView, TextView[] answersView, int phonemeclicked) {
        this.testSet = testSet;
        this.levelGroup = levelGroup;
        this.targetView = targetView;
        this.answersView = answersView;
        this.phonemeclicked = phonemeclicked;
    }

    @Override
    public void onClick(View view) {
        this.item = this.levelGroup.getSubsetIndex();
        this.currentWord = this.testSet[item];
        int i = currentWord.getOmit_index();
        targetView[i].setText(answersView[phonemeclicked].getText());
    }
}

private void crossClick(View view) {
    view.setVisibility(View.INVISIBLE);
    if(view.getTag()==4){
        finish();
    }
}

使用静态变量gameActivity,以便当您完成关卡后,外部课程可以显示GameActivity.gameActivity.finish()后显示您有多少颗星星对于关卡(它也用于在另一个外部类中调用GameActivity.gameActivity.findViewById)。

public class ShowStarsWithDelay extends Handler {

public void handleMessage(Message msg) {
    ImageView starView = (ImageView) ((LevelEndScreens) msg.obj).starView;
    ImageView highscoreView = (ImageView) ((LevelEndScreens) msg.obj).highscoreView;
    int num_currentstars = (int) ((LevelEndScreens) msg.obj).num_currentstars;
    int num_finalstars = (int) ((LevelEndScreens) msg.obj).num_finalstars;
    Boolean highscore = (Boolean) ((LevelEndScreens) msg.obj).highscore;
    int[] goldstars = (int[])((LevelEndScreens) msg.obj).goldstars;

    if(num_currentstars == num_finalstars) {
        if(!highscore) {
            starView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    GameActivity.gameActivity.finish();
                }
            });
        }
        else {
            highscoreView.setImageResource(R.drawable.highscore);
            highscoreView.setVisibility(View.VISIBLE);
            highscoreView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    GameActivity.gameActivity.finish();
                }
            });
        }
    }
    else {
        starView.setImageResource(goldstars[num_currentstars++]);
        Message message = new Message();
        LevelEndScreens endScreens = new LevelEndScreens(starView, highscoreView, num_currentstars, num_finalstars, highscore, goldstars);
        message.obj = endScreens;
        this.sendMessageDelayed(message, 1000);
    }
}

}

1 个答案:

答案 0 :(得分:2)

通常,您希望避免在应用程序的任何位置对Context进行任何静态引用(当然,这包括Activity个类)。对可能被接受的Context的唯一引用是引用应用程序上下文(因为只有一个,并且它总是在内存中,而你的应用程序仍处于活动状态)。

如果您需要在其中一个子项中引用调用活动,则需要将上下文作为参数传递,或者使用其中一个子视图方法来检索上下文(例如{{1}视图和片段)。

有助于了解内存泄漏的更多信息及其重要性在此处: http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html

例如,在您调用getContext()的代码中,您可以安全地将其更改为:

finish()

总而言之,为了解决您的内存泄漏问题,您需要删除所有highscoreView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (v.getContext() instanceof Activity) { ((Activity)v.getContext()).finish(); } } }); 字段的static关键字。