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

Java Android内存泄漏在设备上,而不是在模拟器上,java,android,memory-leaks,garbage-collection,Java,Android,Memory Leaks,Garbage Collection,我正在写一个游戏来教我儿子一些语音:这是我第一次尝试用Java编程,尽管我以前使用过其他语言。这个游戏有四个活动:一个启动屏幕,在你关闭它之前初始化一系列变量;另一个选择用户;第三,选择游戏的级别;第四个是真正玩这个游戏 我的问题是,如果你反复进出游戏活动,该活动最终会崩溃——logcat显示了一个OOM错误。当我这样做时,观察堆大小,并用MAT查看堆转储,看起来好像我泄漏了整个第四个活动--GC只是没有被触发 我已经尝试了很多方法来追踪和修复漏洞,其中大部分都是在不修复问题的情况下进行的改进(

我正在写一个游戏来教我儿子一些语音:这是我第一次尝试用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();
    }
}

}

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

如果需要对其中一个子对象中的调用活动进行引用,则需要将上下文作为参数传递,或者使用子视图方法之一检索上下文(例如,对于视图和片段,使用
getContext()

以下是有助于理解内存泄漏及其重要性的更多信息:

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

highscoreView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (v.getContext() instanceof Activity) {
            ((Activity)v.getContext()).finish();
        }
    }
});

总而言之,为了修复内存泄漏,您需要删除所有
Context
字段的
static
关键字。

谢谢您——我已经实现了您的建议,并且能够始终使用
getContext
,完全摆脱
Context
字段(不仅仅是
static
关键字)。不幸的是,这没有什么区别:我仍然(a)模拟器上没有内存泄漏(b)手机内存泄漏——每次我启动
GameActivity
游戏时,堆大小都会以大约30Mb的增量增加,直到手机放弃为止……您可能需要重新发布更新后的代码。有很多东西可能包含对上下文的引用,例如可绘图、视图组件等。如果您有任何静态声明的话这不是基本类型,您需要重新评估(从上面的代码中,我看到
myTTS
是静态的,这也会导致泄漏,因为它包含一个上下文引用)。这与任何其他可能的源代码无关。好吧——我想已经通过并删除了所有的
static
非原语——当然包括
myTTS
——加上一些其他的调整,就这样做了。
highscoreView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (v.getContext() instanceof Activity) {
            ((Activity)v.getContext()).finish();
        }
    }
});