我写了一个公共(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/