从其他线程修改Android视图

时间:2014-11-03 00:59:42

标签: java android multithreading

在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中的T​​extView定义中添加了一行,以便识别Hello World文本。

    android:id="@+id/hello_world"

类似措辞问题的其他答案假设其他线程是在代码中明确创建的。这里,TextToSpeech引擎的线程是隐式创建的。如何更改我的代码,以便MainActivity.java的最后一行不会引发错误?

4 个答案:

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