Reputation: 7082
On Android, activities run in the main UI thread and the TextToSpeech engine runs in a different thread. I want to update a view in an activity when the TextToSpeech engine completes playing back an utterance.
If I ignore this, then I get an android.view.ViewRoot$CalledFromWrongThreadException
error when the TextToSpeech engine calls the Activity instance.
Here's my code. The error occurs on the last line of the MainActivity.java script.
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();
}
}
}
I also added a line to the TextView definition in activity_main.xml, so that the Hello World text can be identified.
android:id="@+id/hello_world"
Other answers to similarly-worded questions assume that the other thread is created explicitly in the code. Here, the thread for the TextToSpeech engine is created implicitly. How can I change my code so that the last line of MainActivity.java does not throw an error?
Upvotes: 2
Views: 4724
Reputation: 9887
To execute something on the UI thread when outside of it, you can use the method runOnUiThread(Runnable)
that belongs to Activity
.
So you could do:
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
}
});
Upvotes: 2
Reputation: 4262
Any updates to the user interface have to happen within the UI thread, because that is where the graphics context is handled. Your callback method therefore must also occur within the UI thread.
With a reference to the running Activity
, you can call the runOnUIThread(Runnable r)
method, which will synchronize your call within the graphics context. For this specific application, the following implementation could be used:
public void onUtteranceCompleted(String utteranceID) {
if (utteranceID.equals(TAG)) {
activity.runOnUIThread(new Runnable() {
@Override
public final void run() {
activity.ttsUtteranceComplete();
}
});
}
}
To clean up your code, it could be worth making the runOnUIThread()
call an element of the ttsUtteranceComplete()
method. This would ensure that every call would be synchronized.
Upvotes: 1
Reputation: 2326
Use a Handler on the UI thread of your Activity and then post a message to it from your background thread:
https://developer.android.com/training/multiple-threads/communicate-ui.html#Handler
Upvotes: 0
Reputation: 6554
Use Activity.runOnUIThread(Runnable)
- in your case it will be activity.runOnUIThread( new Runnable() { // call a method in your main activity here to update UI element});
Upvotes: 0