我写了一个公共(public)函数来说话,我觉得下面的代码不是很好,但我不知道如何改进它,你能给我一些建议吗?谢谢!

我认为静态 var 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();
    }

}

最佳答案

即使您在静态方法中定义 TextToSpeech,它们通常是从 Activity 上下文调用的,因此只要使用 TextToSpeech 的 Activity 被销毁,您仍然可以关闭 TextToSpeech。该文件指出:



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

另一方面,每次使用后都没有必要关闭它,所以我建议只要 Activity 正在运行,就保持 TextToSpeech 对象的初始化状态。这可以防止在每次“说话”操作之前初始化 TextToSpeech。一旦设备上的 TextToSpeech 被初始化,它通常不是一个非常繁重的操作,但它仍然是你会获得的几毫秒。

在我的 Nexus 5 上启动 TTS 最初需要大约 1.3 秒,之后每个实例化需要 50 到 80 毫秒,这就是您真正可以节省的时间。

如果您担心内存泄漏,请使用应用程序上下文来初始化 TTS(通过使用 context.getApplicationContext() 而不是通常是 Activity 上下文的上下文)。

此外 - 正如 nKn 建议的 - 使用 SoftReference 允许 GC 在 VM 内存不足时回收它(保证所有 SoftReferences 将在 VM 抛出 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。此外,它仅使用应用程序上下文,因此那里没有内存泄漏。使用应用程序上下文启动 Activity 也不是问题,因为我们使用的是 FLAG_ACTIVITY_NEW_TASK。

即使您在非 Activity 上下文(例如 BroadCastReceiver)中使用 TTS,您仍然有某种生命周期可以初始化和销毁​​ TextToSpeech 对象(并释放底层资源)。 IMO 这并不是真正必要的,尤其是在使用 SoftReference 时。

请注意,虽然 BroadcastReceiver 具有生命周期,但文档指出:



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

这意味着您不能在 BroadcastReceiver 中停止 TTS,因为通话是异步的,您不能等到它完成再销毁 TTS 对象。如果你想销毁 TTS 对象(我再次认为没有必要)那么你需要开始例如服务(或没有 ui 的 Activity )。该服务将调用 speak 方法并等待 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 对象,那么实际上没有必要使用静态代码。

关于android - 如何释放 TextToSpeech 的静态变量以避免泄漏?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23073577/

10-10 09:55