我正在写一个游戏,以帮助教我的儿子一些语音:这是我第一次尝试编程 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);
}
}
}
答案 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
关键字。