Java Android内存泄漏在设备上,而不是在模拟器上
我正在写一个游戏来教我儿子一些语音:这是我第一次尝试用Java编程,尽管我以前使用过其他语言。这个游戏有四个活动:一个启动屏幕,在你关闭它之前初始化一系列变量;另一个选择用户;第三,选择游戏的级别;第四个是真正玩这个游戏 我的问题是,如果你反复进出游戏活动,该活动最终会崩溃——logcat显示了一个OOM错误。当我这样做时,观察堆大小,并用MAT查看堆转储,看起来好像我泄漏了整个第四个活动--GC只是没有被触发 我已经尝试了很多方法来追踪和修复漏洞,其中大部分都是在不修复问题的情况下进行的改进(例如,从该活动中删除所有非静态的内部类)。然而,我刚刚尝试在模拟器上运行相同的东西(与我的设备相同的目标和API),并且没有泄漏——堆大小上下波动,GC定期触发,它不会崩溃 所以我打算在这里发布活动的代码,并请求帮助找出可能导致泄漏的原因,但我不再确定这是正确的问题。相反,我想知道为什么它可以在模拟器上工作,而不能在手机上工作。。。有人有什么想法吗 IDE:Android Studio 2.1Java Android内存泄漏在设备上,而不是在模拟器上,java,android,memory-leaks,garbage-collection,Java,Android,Memory Leaks,Garbage Collection,我正在写一个游戏来教我儿子一些语音:这是我第一次尝试用Java编程,尽管我以前使用过其他语言。这个游戏有四个活动:一个启动屏幕,在你关闭它之前初始化一系列变量;另一个选择用户;第三,选择游戏的级别;第四个是真正玩这个游戏 我的问题是,如果你反复进出游戏活动,该活动最终会崩溃——logcat显示了一个OOM错误。当我这样做时,观察堆大小,并用MAT查看堆转储,看起来好像我泄漏了整个第四个活动--GC只是没有被触发 我已经尝试了很多方法来追踪和修复漏洞,其中大部分都是在不修复问题的情况下进行的改进(
目标: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();
}
}
});