TextToSpeech и утечка памяти

У меня были сбои приложений из-за нехватки памяти (в программе, а не в программисте). MAT показывает, что копии моей Activity иногда сохранялись при поворотах экрана, и единственным объектом, поддерживающим поддельные копии, был объект TextToSpeech каждого экземпляра. Я могу продублировать это поведение, используя этот фрагмент:

public class MainActivity extends Activity {
    TextToSpeech    mTts;
    char[]          mBigChunk = new char[1000000];  // not used; just makes MainActivity instances easier to see in MAT

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public void onStart() {
        super.onStart();
        if (mTts==null)                             // shouldn't be necessary and doesn't make any difference
            mTts = new TextToSpeech(this, null);        // commenting this out fixes the leak
    }
    @Override
    public void onStop() {
        super.onStop();
        if (mTts != null) {
            mTts.shutdown();
            mTts = null;        // shouldn't be necessary and doesn't make any difference
        }
    }
}

После 30 изменений ориентации MAT перечисляет от одного до восьми экземпляров net.catplace.tts_leak.MainActivity, а также несколько экземпляров различных объектов TTS; например:

Class Name                                                            | Shallow Heap | Retained Heap | Percentage
------------------------------------------------------------------------------------------------------------------
android.speech.*                                                      |              |               |           
android.speech.tts.TextToSpeech$Connection$1 @ 0x42de94c8 Native Stack|           24 |     2,052,664 |     11.85%
android.speech.tts.TextToSpeech$Connection$1 @ 0x431dd500 Native Stack|           24 |     2,052,664 |     11.85%
android.speech.tts.TextToSpeech$Connection$1 @ 0x435cc438 Native Stack|           24 |           552 |      0.00%
android.speech.tts.TextToSpeech$Connection @ 0x441b3698               |           32 |           528 |      0.00%
android.speech.tts.TextToSpeech @ 0x43fb3c00                          |           64 |           496 |      0.00%
android.speech.tts.TextToSpeech$Connection @ 0x43fb4420               |           32 |            48 |      0.00%
android.speech.tts.TextToSpeech$Connection$1 @ 0x43fb4440 Native Stack|           24 |            24 |      0.00%
android.speech.tts.TextToSpeech$Connection$1 @ 0x441b36b8 Native Stack|           24 |            24 |      0.00%
Total: 8 entries (13,079 filtered)                                    |              |               |           
------------------------------------------------------------------------------------------------------------------

MAT указывает, что поддельные копии MainActivity сохраняются TTS:

Class Name                                                                            | Shallow Heap | Retained Heap
---------------------------------------------------------------------------------------------------------------------
                                                                                      |              |              
net.catplace.tts_leak.MainActivity @ 0x437c6068                                       |          200 |     2,001,352
'- mContext android.speech.tts.TextToSpeech @ 0x431de6d8                              |           64 |           496
   '- this$0 android.speech.tts.TextToSpeech$Connection @ 0x441b3698                  |           32 |           528
      '- this$1 android.speech.tts.TextToSpeech$Connection$1 @ 0x441b36b8 Native Stack|           24 |            24
---------------------------------------------------------------------------------------------------------------------

Я получаю такое поведение на ряде реальных устройств и AVD. Приведенные выше результаты получены на Nexus 7.

Я пробовал разные движки TTS, используя разные события для создания и уничтожения mTts и т. д.

Моя гипотеза заключается в том, что TextToSpeech не всегда обнуляет свою ссылку на контекст, который ее создал, что приводит к утечке копий контекста (Activity). Но я новичок в этом; есть что-то, что я делаю неправильно?


person Peter McLennan    schedule 29.10.2013    source источник
comment
Если объект TextToSpeech создается путем передачи getApplicationContext(), а не this (Activity), проблема, похоже, не возникает. Это имеет смысл, поскольку приложение не уничтожается при изменении ориентации. Хотя, мне кажется, в этом нет необходимости.   -  person Peter McLennan    schedule 31.10.2013
comment
У меня такая же проблема. Поворот экрана приводил к утечке информации о моей активности. Поверните 10 раз, 10 копий активности в памяти. Я изменил его, чтобы передать getApplicationContext() вместо этого объекту TextToSpeech, как предложил Питер, и это решило проблему. Бум!   -  person Bryan Bedard    schedule 12.07.2014


Ответы (2)


Взглянем на исходный код TextToSpeech здесь, вы заметите, что он фактически привязывается служба для переданного контекста, и метод завершения работы фактически отменяет ее привязку. Остается только догадываться, так как у службы есть свой жизненный цикл, а TextToSpeech может сдерживать контекст.

Теперь я также не уверен, что это может означать, но я открыт для любых новых открытий с вашей стороны. Учитывая, что TextToSpeech — это служба, вы можете передать ей контекст приложения, поскольку служба все еще будет работать, когда активность будет уничтожена.

Также для дальнейшего чтения ___< em>___________

person Parvaz Bhaskar    schedule 31.10.2013
comment
Спасибо еще раз. Я заметил, что передача контекста приложения, похоже, устраняет проблему. Жаль, что в примерах Google используется контекст Activity. Ваша вторая ссылка показывает, что служба пытается делать умные (и асинхронные) вещи, которые могут объяснить непоследовательное поведение утечки памяти, которое я заметил. Вероятно, все зависит от того, насколько быстро происходят изменения ориентации. - person Peter McLennan; 01.11.2013
comment
stackoverflow.com/questions/7298731/ ответ Commonsware, хотя он и не имеет прямого отношения к теме обсуждений, дает хорошее представление о том, какой контекст использовать, когда. - person Parvaz Bhaskar; 01.11.2013
comment
Отличная находка. Я думаю, что это может иметь прямое отношение, поскольку в нем говорится об использовании контекста приложения для служб (что, как вы указывали ранее, представляет собой TTS). Обратные примеры Google, казалось бы, поощряют утечки. - person Peter McLennan; 01.11.2013
comment
Спасибо, только один вопрос, если передать контекст приложения, проблема исчезнет? в смысле, MAT показывает какие-то оставшиеся экземпляры? - person Parvaz Bhaskar; 01.11.2013
comment
Страннее и страннее. Когда я передаю контекст приложения, несколько экземпляров android.speech.tts.TextToSpeech$Connection$1 все еще сохраняются (не для каждого поворота, но, может быть, 1/3). Они не занимают много памяти, потому что они не содержат ссылку на действие, и несколько копий действия не накапливаются. Однако, когда TTS создается с передачей контекста Activity, экземпляры android.speech.tts.TextToSpeech$Connection$1 накапливаются примерно с той же скоростью, но намного больше, поскольку каждый из них включает экземпляр Activity. MAT также показывает несколько экземпляров Activity, но не по одному на TTS! - person Peter McLennan; 02.11.2013
comment
Заманчиво думать, что я просто наблюдаю временное поведение, вызванное недетерминированным поведением сборщика мусора (т. е. сборщик мусора просто не обнаружил необходимости или не удосужился очистить ненужные экземпляры TTS или моего Acitivy). Однако, если бы это было так, приложение не должно было бы аварийно завершать работу, потому что сборщик мусора был бы вынужден действовать заранее. Однако приложение может ОПРЕДЕЛЕННО вылететь из-за OOM. Это можно продемонстрировать, увеличив размер mBigChunk. Если я удвою его, мой Nexus 7 умрет примерно после 10 изменений ориентации (когда экземпляр TTS создается с использованием контекста приложения). - person Peter McLennan; 02.11.2013

Если не реализовано android:configChanges="orientation", вы можете переопределить метод onDestory:

@Override
protected void onDestroy() {
    super.onDestroy();
    if (ttsEngine != null) {
        ttsEngine.stop();
        ttsEngine.shutdown();
        Log.d(TAG, "TTS destroyed");
    }
}
person Mohsen Afshin    schedule 29.10.2013
comment
Пробовал это. До сих пор течет (иногда). - person Peter McLennan; 29.10.2013