Я пишу игру, чтобы помочь учить моего сына некоторой фонетике: это моя первая попытка программирования в Java, хотя я ранее использовал другие языки. В игре четыре действия: заставка, которая инициализирует массив переменных, прежде чем вы его убьете; другой - выбрать пользователя; третий, чтобы выбрать, какой уровень игры играть; и четвертый, чтобы на самом деле сыграть в игру.Утечка памяти Android на устройстве, а не на эмуляторе
Моя проблема заключалась в том, что если вы снова и снова входите в и из игровой активности, эта деятельность в конечном итоге будет аварийна - logcat показал ошибку OOM. Наблюдая за размером кучи, как я это делал, и глядя на кучу кучи с MAT, казалось, что я пропустил всю четвертую активность - GC просто не запускался.
Я пробовал много вещей, чтобы отследить и устранить утечку - большинство из них, я уверен, что улучшения (например, избавление от всех нестатических внутренних классов от этого действия) без исправления проблемы. Тем не менее, я просто попробовал запустить ту же самую вещь на эмуляторе (та же цель и API как мое устройство), и нет утечки - размер кучи идет вверх и вниз, GC регулярно запускается, он не падает.
Так что я собирался опубликовать здесь код для этой деятельности и попросить о помощи узнать, что может вызвать утечку, но я не уверен, что это правильный вопрос. Вместо этого мне интересно, почему это работает на эмуляторе, но не на телефоне ... У кого-нибудь есть идеи?
IDE: Android студия 2,1
Target: Android 6, API 23 (Minimum SDK 8)
Emulator: Android Студия
устройства: 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);
}
}
}
Благодарим за это. Я внедрил ваше предложение и смог использовать 'getContext' всюду, полностью избавляясь от полей' Context' (а не только ключевого слова 'static'). К сожалению, это не имеет никакого значения: у меня все еще есть (а) отсутствие утечки памяти на эмуляторе (b) утечка памяти на моем телефоне. Размер кучи увеличивается с шагом около 30 МБ каждый раз, когда я запускаю GameActivity до тех пор, пока телефон не даст up ... –
Возможно, вам потребуется повторная публикация обновленного кода. Есть много вещей, которые могут содержать ссылки на контексты, такие как чертежи, компоненты просмотра и т. Д. Если у вас есть какие-либо статические объявления вообще, которые не являются примитивными типами, вам нужно будет переоценить (из вашего кода выше, я см., что 'myTTS' является статическим, что также вызовет утечку, поскольку содержит ссылку на контекст). Это помимо других возможных источников. –
ОК - прошел и избавился от (как я думаю) всех «статических» непримитивов - конечно, включая «myTTS» - плюс некоторые другие хитрости, и это произошло. –