泄露了ServiceConnection android.speech.tts.TextToSpeech $ Connection

时间:2015-10-15 09:44:00

标签: android android-fragments memory-leaks

我有一个文本到语音片段。片段由按钮组成,如果按下按钮,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);
            }
        });
    }
}

1 个答案:

答案 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