Я пишу общедоступную функцию для озвучивания текста, я не думаю, что следующий код хорош, но я не знаю, как его улучшить, не могли бы вы дать мне несколько предложений? Спасибо!

Я думаю, что статическая переменная TextToSpeech tts может вызвать утечку, я не знаю, как ее освободить.

public class SpeechTxt {

    private static TextToSpeech tts;

    public static void SpeakOut(final Context myContext, String s) {        

        tts = new TextToSpeech(myContext, new TextToSpeech.OnInitListener(){
            @Override
            public void onInit(int status) {
                // TODO Auto-generated method stub
                if (status == TextToSpeech.SUCCESS) {   

                    int result = tts.setLanguage(Locale.US);

                    if (result == TextToSpeech.LANG_MISSING_DATA
                            || result == TextToSpeech.LANG_NOT_SUPPORTED) {
                        Toast.makeText(myContext, "Language is not supported",Toast.LENGTH_SHORT).show();
                    } else {
                        tts.speak("Hello, the world! "+s, TextToSpeech.QUEUE_ADD, null);
                    }           

                }else {
                    Toast.makeText(myContext, "Initilization Failed",Toast.LENGTH_SHORT).show();
                }
            }

        });

        /* I must comment the code, or phone can't speak
        if (tts != null) {
            tts.stop();
            tts.shutdown();
        } ;
        */ 

        Toast.makeText(myContext, "This is a test",Toast.LENGTH_SHORT).show();
    }

}
4
HelloCW 15 Апр 2014 в 06:17

2 ответа

Лучший ответ

Даже если вы определяете TextToSpeech в статических методах, они обычно вызываются из контекста Activity, поэтому вы все равно можете выключить TextToSpeech всякий раз, когда Activity, использующее TextToSpeech, уничтожается. В документации указано:

Например, рекомендуется вызывать этот метод в методе onDestroy () Activity, чтобы можно было полностью остановить механизм TextToSpeech.

http://developer.android.com/reference/android/speech/tts/TextToSpeech.html#shutdown ()

С другой стороны, нет необходимости выключать его после каждого использования, поэтому я предлагаю сохранять инициализированный объект TextToSpeech, пока выполняется Activity. Это предотвращает инициализацию TextToSpeech перед каждой операцией «говорить». Обычно это не очень сложная операция после инициализации TextToSpeech на устройстве, но вы все равно получите пару мсек.

Запуск TTS на моем Nexus 5 первоначально занимает около 1,3 секунды, а после этого каждый экземпляр занимает от 50 до 80 мс, и это время, которое вы действительно можете сэкономить.

Если вас беспокоит утечка памяти, используйте контекст приложения для инициализации TTS (используя context.getApplicationContext () вместо простого контекста, который обычно является контекстом Activity).

Также - как предлагает nKn - используйте SoftReference, чтобы позволить GC перерабатывать его, если у виртуальной машины заканчивается память (гарантируется, что все SoftReferences будут переработаны до того, как виртуальная машина выдаст OutOfMemoryError, см.: http://docs.oracle.com/javase/7/docs/api/java/ lang / ref / SoftReference.html ).

Для дальнейшего улучшения вашего кода вы должны обработать пропущенный языковой регистр, разрешив пользователю установить язык.

Вот мое предложение, как вы могли бы улучшить свой код:

public class SpeechTxt {

    private static SoftReference<TextToSpeech> sTts;

    public static void speakOut(final Context context, final String s) {
        final Context appContext = context.getApplicationContext();
        if (sTts == null) {
            sTts = new SoftReference<TextToSpeech>(new TextToSpeech(appContext, new TextToSpeech.OnInitListener(){
                @Override
                public void onInit(int status) {
                    if (status == TextToSpeech.SUCCESS) {
                        speak(appContext, s);
                    }
                    else {
                        loadText2SpeechData(appContext);
                    }
                }
            }));
        }
        else {
            speak(appContext, s);
        }
    }

    public static void destroyTTS(Context context) {
        if (sTts != null && ! sTts.get().isSpeaking()) {
            sTts.get().shutdown();
            sTts = null;
        }
    }

    private static void speak(Context context, String s) {
        if (sTts != null) {
            switch (sTts.get().setLanguage(Locale.getDefault())) {
            case TextToSpeech.LANG_COUNTRY_AVAILABLE:
            case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
            case TextToSpeech.LANG_AVAILABLE: {
                sTts.get().speak(s, TextToSpeech.QUEUE_ADD, null);
                break;
            }
            case TextToSpeech.LANG_MISSING_DATA: {
                loadText2SpeechData(context);
                break;
            }
            case TextToSpeech.LANG_NOT_SUPPORTED: // not much to do here
            }
        }
    }

    private static void loadText2SpeechData(Context context) {
        try {
            Intent installIntent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
            installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(installIntent);
        }
        catch (ActivityNotFoundException ignore) {}
    }
}

Как видите, TextToSpeech создается только в том случае, если этого не было сделано до или после уничтожения объекта. Кроме того, он использует только контекст приложения, поэтому там не происходит утечки памяти. Запуск действия с контекстом приложения не является проблемой, потому что мы используем FLAG_ACTIVITY_NEW_TASK.

Даже если вы используете TTS в контексте, отличном от Activity (например, BroadCastReceiver), у вас все равно будет какой-то жизненный цикл, который позволит инициализировать и уничтожить объект TextToSpeech (и освободить базовые ресурсы). ИМО, в этом нет необходимости, особенно при использовании SoftReference.

Обратите внимание, что хотя у BroadcastReceiver есть жизненный цикл, в документации указано, что:

Объект BroadcastReceiver действителен только на время вызова onReceive (Context, Intent). Как только ваш код возвращается из этой функции, система считает, что объект завершен и больше не активен.

Это имеет важные последствия для того, что вы можете сделать в реализации onReceive (Context, Intent): все, что требует асинхронной операции, недоступно, потому что вам нужно будет вернуться из функции для обработки асинхронной операции, но в этот момент BroadcastReceiver больше не активен, и поэтому система может убить свой процесс до завершения асинхронной операции.

http://developer.android.com/reference/android/content/BroadcastReceiver.html#ReceiverLifecycle

Это означает, что вы не можете остановить TTS в BroadcastReceiver, потому что вызов разговора является асинхронным, и вы не можете дождаться его завершения, чтобы затем уничтожить объект TTS. Если вы хотите уничтожить объект TTS (опять же, я не думаю, что это необходимо), вам нужно будет начать, например. Сервис (или Действие без пользовательского интерфейса). Служба вызовет метод Speech и будет ждать, пока OnUtteranceCompletedListener (или OnUtteranceProgressListener) не вернется, например как это:

sTts.get().setOnUtteranceCompletedListener(sListener);

HashMap<String, String> params = new HashMap<String, String>();
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, s);
sTts.get().speak(s, TextToSpeech.QUEUE_ADD, params);

private static OnUtteranceCompletedListener sListener = new OnUtteranceCompletedListener() {
    @Override
    public void onUtteranceCompleted(String utteranceId) {
        if (! sTts.get().isSpeaking()) {
            destroyTTS();           
        }
    }
};

Кстати, если вы хотите уничтожить объект TTS после каждого выступления, тогда действительно нет необходимости использовать статический код.

8
Emanuel Moecklin 25 Апр 2014 в 04:39

Любой объект, созданный с помощью new, сохраняется в куче. Каждый раз, когда запускается GC, он освобождает объекты, которые можно освободить, что означает, что (среди прочих обстоятельств) он освобождает любой объект, недоступный по какой-либо другой ссылке.

Если вас беспокоит тот факт, что это может привести к утечке, просто установите для него значение null, как только закончите. Другой способ - использовать SoftReference на этом объекте. Это сообщит GC, что этот объект имеет предпочтение для освобождения, поэтому, если Android OS испытывает нехватку памяти, он будет иметь некоторые предпочтения для освобождения по сравнению с другими объектами. В этом случае вам просто нужно сделать что-то вроде этого:

private static SoftReference<TextToSpeech> tts;
...
tts = new SoftReference<TextToSpeech>(...);

Тогда объект будет достигнут с помощью tts.get(), а не только tts. Если вы используете этот подход, важно, чтобы каждый раз, когда вы хотите использовать этот объект, вы проверяли, является ли он null или нет, поскольку теперь более вероятно, что он будет освобожден GC.

if (tts != null) { ... }

Однако лучший способ узнать, есть ли у вас утечка памяти, - это проверить ее эмпирическим путем. Для этого я использую DDMS + HPROF, который сделает дамп памяти, чтобы вы могли проанализировать его и посмотреть, выделен ли объект большему количеству памяти, чем должен. Эта тема очень обширна и требует некоторой практики, но я оставлю вам несколько ссылок, которые помогли мне многое узнать по этой теме.

2
nKn 18 Апр 2014 в 09:50