在Android上,活动在主UI线程中运行,TextToSpeech引擎在不同的线程中运行。我希望在TextToSpeech引擎完成播放话语时更新活动中的视图。
如果我忽略了这一点,那么当TextToSpeech引擎调用Activity实例时,我会收到android.view.ViewRoot$CalledFromWrongThreadException
错误。
这是我的代码。该错误发生在MainActivity.java脚本的最后一行。
TTSUser.java
package com.example.thread;
interface TTSUser {
void ttsUtteranceComplete();
}
MainActivity.java
package com.example.thread;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity implements TTSUser {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new TTS(this);
}
public void ttsUtteranceComplete() {
TextView view_to_hide = (TextView) findViewById(R.id.hello_world);
// On next line: android.view.ViewRoot$CalledFromWrongThreadException:
// Only the original thread that created a view hierarchy can touch its
// views.
view_to_hide.setVisibility(TextView.GONE);
}
}
TTS.java
package com.example.thread;
import android.app.Activity;
import android.content.Context;
import android.speech.tts.TextToSpeech;
import java.util.HashMap;
import java.util.Locale;
public class TTS implements TextToSpeech.OnInitListener, TextToSpeech.OnUtteranceCompletedListener {
private final String TAG = "callback";
private static TextToSpeech tts;
private TTSUser activity;
public TTS(TTSUser activity) { // Ensures access to ttsUtteranceComplete()
this.activity = activity;
Context context = ((Activity) activity).getApplicationContext();
tts = new android.speech.tts.TextToSpeech(context, this);
}
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
tts.setLanguage(Locale.UK);
tts.setOnUtteranceCompletedListener(this);
speakText("Hello World");
}
}
public void speakText(String toSpeak) {
int mode = android.speech.tts.TextToSpeech.QUEUE_FLUSH;
// Create an id for this utterance, so that we can call back when it's done
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, TAG);
tts.speak(toSpeak, mode, hashMap);
}
public void onUtteranceCompleted(String utteranceID) {
if (utteranceID.equals(TAG)) {
activity.ttsUtteranceComplete();
}
}
}
我还在activity_main.xml中的TextView定义中添加了一行,以便识别Hello World文本。
android:id="@+id/hello_world"
类似措辞问题的其他答案假设其他线程是在代码中明确创建的。这里,TextToSpeech引擎的线程是隐式创建的。如何更改我的代码,以便MainActivity.java的最后一行不会引发错误?
答案 0 :(得分:1)
要在UI线程外部执行某些操作,可以使用属于runOnUiThread(Runnable)
的方法Activity
。
所以你可以这样做:
activity.runOnUiThread(new Runnable() { // EDIT: ...Ui... requires a lowercase "i"
@Override
public final void run()
// this runs on UI thread
activity.ttsUtteranceComplete(); // this function will run on the UI thread
}
});
答案 1 :(得分:1)
用户界面的任何更新都必须在UI线程内进行,因为这是处理图形上下文的地方。因此,您的回调方法也必须在UI线程中进行。
通过引用正在运行的Activity
,您可以调用runOnUIThread(Runnable r)
方法,该方法将在图形上下文中同步您的调用。对于此特定应用程序,可以使用以下实现:
public void onUtteranceCompleted(String utteranceID) {
if (utteranceID.equals(TAG)) {
activity.runOnUIThread(new Runnable() {
@Override
public final void run() {
activity.ttsUtteranceComplete();
}
});
}
}
要清理代码,可能值得runOnUIThread()
调用ttsUtteranceComplete()
方法的元素。这将确保每个呼叫都是同步的。
答案 2 :(得分:0)
使用Activity.runOnUIThread(Runnable)
- 在您的情况下,它将是activity.runOnUIThread( new Runnable() { // call a method in your main activity here to update UI element});
答案 3 :(得分:0)
在Activity的UI线程上使用Handler,然后从后台线程向其发布消息:
https://developer.android.com/training/multiple-threads/communicate-ui.html#Handler