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初始化。