How to release the static var of TextToSpeech in order to avoid leak?
android
Solution
Even if you define the TextToSpeech in static methods they are usually be called from an Activity context, so you can still shutdown the TextToSpeech whenever an Activity that uses TextToSpeech is destroyed. The documentation states:
It is good practice for instance to call this method in the onDestroy() method of an Activity so the TextToSpeech engine can be cleanly stopped.
http://developer.android.com/reference/android/speech/tts/TextToSpeech.html#shutdown()
On the other hand it's not necessary to shut it down after each use so I suggest to keep the TextToSpeech object initialized as long as the Activity is running. This prevents the TextToSpeech to be initialized before each "speak" operation. It's usually not a very heavy operation once the TextToSpeech on the device has been initialized but it's still a couple of ms you'll gain.
Starting the TTS on my Nexus 5 takes initially about 1.3 seconds and after that each instantiation takes between 50 and 80 ms and that's time you can really save.
If you are concerned about memory leaks use the application context to initialize the TTS (by using context.getApplicationContext() instead of just context which would usually be an Activity context).
Also - as nKn suggested - use a SoftReference to allow the GC to recycle it should the VM run low on memory (it's guaranteed that all SoftReferences will be recycled before the VM throws an OutOfMemoryError, see: http://docs.oracle.com/javase/7/docs/api/java/lang/ref/SoftReference.html).
To further improve your code you should handle the missing language case by allowing the user to install the language.
Here's my suggestion how you could enhance your code:
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) {}
}
}
As you can see TextToSpeech is only instantiated if that hasn't been done before or after the object has been destroyed. Also it uses only the application context so no memory leaks there. Starting an activity with the application context isn't a problem either because we are using FLAG_ACTIVITY_NEW_TASK.
Even if you use the TTS in a non Activity context (e.g. a BroadCastReceiver) you'd still have some kind of lifecycle that would allow to initialize and destroy the TextToSpeech object (and free the underlying resources). IMO that's not really necessary especially when using a SoftReference.
Note that while a BroadcastReceiver has a lifecycle the documentation states that:
A BroadcastReceiver object is only valid for the duration of the call to onReceive(Context, Intent). Once your code returns from this function, the system considers the object to be finished and no longer active.
This has important repercussions to what you can do in an onReceive(Context, Intent) implementation: anything that requires asynchronous operation is not available, because you will need to return from the function to handle the asynchronous operation, but at that point the BroadcastReceiver is no longer active and thus the system is free to kill its process before the asynchronous operation completes.
http://developer.android.com/reference/android/content/BroadcastReceiver.html#ReceiverLifecycle
This means you can't stop the TTS in a BroadcastReceiver because the speak call is asynchronous and you can't wait till it finishes to then destroy the TTS object. If you want to destroy the TTS object (again I don't think it's necessary) then you'd need to start e.g. a Service (or an Activity without ui). The service would call the speak method and wait till the OnUtteranceCompletedListener (or OnUtteranceProgressListener) returns, e.g. like that:
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();
}
}
};
BTW if you want to destroy the TTS object after each speak then there's really no need to use static code.
Problem
I write a public function to speak a text, I don't think the following code is good, but I don't know how to improve it, could you give me some suggestions? Thanks! I think the static var TextToSpeech tts maybe cause leak,I don't know how to release it. ``` 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(); } } ```