天天看点

【问题记录】Android TTS 初始化成功之后,隔一段时间服务连接断开speak failed : not bound to tts enginespeak failed : not bound to tts engine

1、问题描述

    使用Android 系统自带的TTS把文字转成语音播放,初始化成功也能播放成功,但是有些小内存手机隔一段时间再次把文字转为语音时会报 :

speak failed : not bound to tts engine

2、问题所在

查找TextToSpeech.java的源码时发现如下代码:

private <R> R runAction(Action<R> action, R errorResult, String method,
            boolean reconnect, boolean onlyEstablishedConnection) {
        synchronized (mStartLock) {
            if (mServiceConnection == null) {
                Log.w(TAG, method + " failed: not bound to TTS engine");
                return errorResult;
            }
            return mServiceConnection.runAction(action, errorResult, method, reconnect,
                    onlyEstablishedConnection);
        }
    }
           

调用TextToSpeech.speak(xxx),执行runAction方法时mServiceConnection == null所以打印了错误日志

speak failed : not bound to tts engine

mServiceConnection 是什么?是TextToSpeech类中的一个字段

private Connection mServiceConnection;
           

那么Connection有是什么呢?

private class Connection implements ServiceConnection {
        private ITextToSpeechService mService;

        private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;

        private boolean mEstablished;

        private final ITextToSpeechCallback.Stub mCallback =
                new ITextToSpeechCallback.Stub() {
                    public void onStop(String utteranceId, boolean isStarted)
                            throws RemoteException {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onStop(utteranceId, isStarted);
                        }
                    };

                    @Override
                    public void onSuccess(String utteranceId) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onDone(utteranceId);
                        }
                    }

                    @Override
                    public void onError(String utteranceId, int errorCode) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onError(utteranceId);
                        }
                    }

                    @Override
                    public void onStart(String utteranceId) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onStart(utteranceId);
                        }
                    }

                    @Override
                    public void onBeginSynthesis(
                            String utteranceId,
                            int sampleRateInHz,
                            int audioFormat,
                            int channelCount) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onBeginSynthesis(
                                    utteranceId, sampleRateInHz, audioFormat, channelCount);
                        }
                    }

                    @Override
                    public void onAudioAvailable(String utteranceId, byte[] audio) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onAudioAvailable(utteranceId, audio);
                        }
                    }

                    @Override
                    public void onRangeStart(String utteranceId, int start, int end, int frame) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onRangeStart(utteranceId, start, end, frame);
                        }
                    }
                };
           

上面源码可以看到Connection是ServiceConnection的实现类。

TextToSpeech初始化时通过TextToSpeech#connectToEngine使当前上下文与TTS服务绑定:

private boolean connectToEngine(String engine) {
        Connection connection = new Connection();
        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
        intent.setPackage(engine);
        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
        if (!bound) {
            Log.e(TAG, "Failed to bind to " + engine);
            return false;
        } else {
            Log.i(TAG, "Sucessfully bound to " + engine);
            mConnectingServiceConnection = connection;
            return true;
        }
    }
           

回到问题: 有些小内存手机隔一段时间再次把文字转为语音时会检测到mServiceConnection == null此时context与TTS服务之间的绑定已经断开,所以打印错误日志。

3.解决办法

我这边的解决办法是,在播放文字之前先通过反射检查mServiceConnection 是否为空

代码如下:

/**
     * TTS初始化之后有时会无法播放语音。
     * 从打印日志看failed: not bound to TTS engine
     * 找到源代码打印处
     * if (mServiceConnection == null) {
     *      Log.w(TAG, method + " failed: not bound to TTS engine");
     *   return errorResult;
     *  }
     *  通过反射判断mServiceConnection是否为空
     * @param tts
     * @return true 可用
     */
    public static boolean ismServiceConnectionUsable(TextToSpeech tts) {

        boolean isBindConnection = true;
        if (tts == null){
            return false;
        }
        Field[] fields = tts.getClass().getDeclaredFields();
        for (int j = 0; j < fields.length; j++) {
            fields[j].setAccessible(true);
            if (TextUtils.equals("mServiceConnection",fields[j].getName()) && TextUtils.equals("android.speech.tts.TextToSpeech$Connection",fields[j].getType().getName())) {
                try {
                    if(fields[j].get(tts) == null){
                        isBindConnection = false;
                        LogUtil.e(TAG, "*******反射判断 TTS -> mServiceConnection == null*******");
                    }
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        return isBindConnection;
    }
           

如果mServiceConnection != null说明服务没有断开可以继续使用之前初始化好的TextToSpeech对象播放文字语音。

如果mServiceConnection == null 就重新进行TextToSpeech初始化。