我有一个文本到语音片段。片段由按钮组成,如果按下按钮,tts会说出给定的文本。
当我尝试使用真实设备HTC HTL21(API 16)运行此片段时,我得到“泄露ServiceConnection android.speech.tts.TextToSpeech $ Connection”
在此泄漏错误之前,我可以看到停止/关闭警告。但我无法弄清楚我的代码有什么问题。
奇怪的是,我没有通过AVD(Android虚拟设备)或Genymotion获得这种警告/错误。
我非常感谢任何帮助...
这是log cat的一部分:
10-15 18:23:45.524 14811-14811/jp.miyamura.hds_r I/TextToSpeech: Sucessfully bound to com.google.android.tts
10-15 18:23:45.574 14811-14811/jp.miyamura.hds_r W/TextToSpeech: stop failed: not bound to TTS engine
10-15 18:23:45.574 14811-14811/jp.miyamura.hds_r W/TextToSpeech: shutdown failed: not bound to TTS engine
10-15 18:23:45.654 14811-14811/jp.miyamura.hds_r I/TextToSpeech: Sucessfully bound to com.google.android.tts
10-15 18:23:45.654 14811-14811/jp.miyamura.hds_r W/TextToSpeech: shutdown failed: not bound to TTS engine
10-15 18:23:45.664 14811-14811/jp.miyamura.hds_r I/TextToSpeech: Sucessfully bound to com.google.android.tts
10-15 18:23:45.794 14811-14811/jp.miyamura.hds_r E/ActivityThread: Activity jp.miyamura.hds_r.MainActivity has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection@40fd08e8 that was originally bound here
10-15 18:23:45.794 14811-14811/jp.miyamura.hds_r E/ActivityThread: android.app.ServiceConnectionLeaked: Activity jp.miyamura.hds_r.MainActivity has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection@40fd08e8 that was originally bound here
这是我的演讲片段:
package jp.miyamura.hds_r;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.os.Build;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import java.util.HashMap;
import java.util.Locale;
public class SpeechFragment extends Fragment implements TextToSpeech.OnInitListener, View.OnClickListener {
private int buttonID = 0;
private TextToSpeech tts;
private String queuedText;
private String splitChar;
// Instantiate Speech fragment itself
public static SpeechFragment newInstance(String speechString, String buttonString, int buttonID, String splitChar) {
// Store data to be passed to the fragment in bundle format
Bundle args = new Bundle();
args.putString("Speech", speechString);
args.putString("ButtonTitle", buttonString);
args.putInt("ButtonID", buttonID);
args.putString("splitChar", splitChar);
// Instantiate Speech fragment and set arguments
SpeechFragment newFragment = new SpeechFragment();
newFragment.setArguments(args);
return newFragment;
}
// Declare interface to communicate with it's container Activity
public interface onSpeechFragmentListener {
void onUtteranceStatus(boolean status);
void onClickStatus(int buttonID);
}
private onSpeechFragmentListener myCallback;
@Override
@SuppressWarnings("deprecation")
public void onAttach(Activity activity) {
super.onAttach(activity);
// Check container Activity implements interface listener or not
try {
myCallback = (onSpeechFragmentListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement onSpeechFragmentListener");
}
}
@Override
public void onDetach() {
super.onDetach();
myCallback = null;
}
// Required empty public constructor
public SpeechFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
@SuppressWarnings("deprecation")
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Instantiate TTS
if (tts == null) tts = new TextToSpeech(getActivity(), this);
// Retrieve root view of fragment
View rootView = inflater.inflate(R.layout.fragment_speech, container, false);
String button_title = "";
splitChar = "";
// Receive arguments from it's container Activity
if (getArguments() != null) {
queuedText = getArguments().getString("Speech");
button_title = getArguments().getString("ButtonTitle");
buttonID = getArguments().getInt("ButtonID");
splitChar = getArguments().getString("splitChar");
}
// Retrieve button and set onClick listener
Button button = (Button) rootView.findViewById(R.id.speech_Button);
button.setText(button_title);
button.setId(buttonID);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // ToDo: Should be Build.VERSION_CODES.MARSHMALLOW
button.setTextAppearance(android.R.style.TextAppearance_Medium);
} else {
button.setTextAppearance(getActivity(), android.R.style.TextAppearance_Medium);
}
button.setOnClickListener(this);
return rootView;
}
@Override
public void onStop() {
if(tts !=null) {
tts.stop();
}
super.onStop();
}
@Override
public void onDestroyView() {
if(tts !=null) {
tts.shutdown();
}
super.onDestroyView();
}
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS && tts != null) {
// Set TTS Locale
tts.setLanguage(Locale.getDefault());
}
}
@Override
public void onClick(View v) {
// tts must not be null
if (tts != null) {
String utteranceId = this.hashCode() + "";
// TTS Branch with Android OS Version
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ttsGreater21(queuedText, utteranceId);
} else {
ttsUnder20(queuedText, utteranceId);
}
}
myCallback.onClickStatus(buttonID);
}
// Speech method before LOLLIPOP
@SuppressWarnings("deprecation")
private void ttsUnder20(String text, String utteranceId) {
HashMap<String, String> map = new HashMap<>();
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
// Split speech text with new line character and speak silence for each separation
String[] splitSpeech = text.split(splitChar);
for (int i = 0; i < splitSpeech.length; i++) {
if (i == 0) { // Use for the first split text to flush on audio stream
tts.speak(splitSpeech[i].trim(),TextToSpeech.QUEUE_FLUSH, map);
} else { // add the new text on previous then play the TTS
tts.speak(splitSpeech[i].trim(), TextToSpeech.QUEUE_ADD,map);
}
// Play Silence between text split
tts.playSilence(750, TextToSpeech.QUEUE_ADD, null); // ToDo Length of silence should be optimized
}
setTTSListener();
}
// Speech method for LOLLIPOP and after
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void ttsGreater21(String text, String utteranceId) {
// Split speech text with new line character and speak with silence
String[] splitSpeech = text.split(splitChar);
for (int i = 0; i < splitSpeech.length; i++) {
if (i == 0) { // Use for the first split text to flush on audio stream
tts.speak(splitSpeech[i].trim(), TextToSpeech.QUEUE_FLUSH, null, utteranceId);
} else { // add the new text on previous then play the TTS
tts.speak(splitSpeech[i].trim(), TextToSpeech.QUEUE_ADD, null, utteranceId);
}
// Play Silence between text split
tts.playSilentUtterance(750, TextToSpeech.QUEUE_ADD, null); // ToDo Length of silence should be optimized
}
setTTSListener();
}
// TTS Status listener method
private void setTTSListener(){
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onStart(String utteranceId) {
}
@Override
public void onDone(String utteranceId) {
myCallback.onUtteranceStatus(true);
}
@Override
public void onError(String utteranceId) {
myCallback.onUtteranceStatus(false);
}
});
}
}
答案 0 :(得分:0)
停止/关闭失败是由屏幕旋转引起的。一旦无法停止/关闭,就会触发泄漏错误。所以与上面的片段代码无关...
在我的活动代码中,我在屏幕旋转上重新创建了片段。我重新创建的原因是我忘记继承onSavedinstancestate。然后片段因屏幕旋转而丢失。
所以对策是:
@Override
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState); // Add this line
savedInstanceState.putString("inputWords", tv_input_word.getText().toString());
}
和
if (savedInstanceState == null) { // Add this line
// Setup Fragment
transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container1,
SpeechFragment.newInstance(speechString, buttonString, buttonID1, splitChar));
transaction.commit();
} // Add this line