1208 Stimmen

Android "Nur der ursprüngliche Thread, der eine Ansichtshierarchie erstellt hat, kann seine Ansichten berühren."

Ich habe einen einfachen Musik-Player in Android gebaut. Die Ansicht für jeden Song enthält eine SeekBar, wie folgt implementiert:

public class Song extends Activity implements OnClickListener,Runnable {
    private SeekBar progress;
    private MediaPlayer mp;

    // ...

    private ServiceConnection onService = new ServiceConnection() {
          public void onServiceConnected(ComponentName className,
            IBinder rawBinder) {
              appService = ((MPService.LocalBinder)rawBinder).getService(); // service that handles the MediaPlayer
              progress.setVisibility(SeekBar.VISIBLE);
              progress.setProgress(0);
              mp = appService.getMP();
              appService.playSong(title);
              progress.setMax(mp.getDuration());
              new Thread(Song.this).start();
          }
          public void onServiceDisconnected(ComponentName classname) {
              appService = null;
          }
    };

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.song);

        // ...

        progress = (SeekBar) findViewById(R.id.progress);

        // ...
    }

    public void run() {
    int pos = 0;
    int total = mp.getDuration();
    while (mp != null && pos<total) {
        try {
            Thread.sleep(1000);
            pos = appService.getSongPosition();
        } catch (InterruptedException e) {
            return;
        } catch (Exception e) {
            return;
        }
        progress.setProgress(pos);
    }
}

Das funktioniert gut. Jetzt möchte ich einen Timer, der die Sekunden/Minuten des Songfortschritts zählt. Also habe ich eine TextView im Layout, erhalten Sie es mit findViewById() en onCreate() und setzen Sie dies ein run() nach progress.setProgress(pos) :

String time = String.format("%d:%d",
            TimeUnit.MILLISECONDS.toMinutes(pos),
            TimeUnit.MILLISECONDS.toSeconds(pos),
            TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(
                    pos))
            );
currentTime.setText(time);  // currentTime = (TextView) findViewById(R.id.current_time);

Aber diese letzte Zeile ist die Ausnahme:

Android.view.ViewRoot$CalledFromWrongThreadException: Nur der ursprüngliche Thread, der eine View-Hierarchie erstellt hat, kann seine Views berühren.

Dennoch tue ich hier im Grunde das Gleiche wie bei der SeekBar - die Erstellung der Ansicht in onCreate und berührt sie dann in run() - und ich bekomme diese Beschwerde nicht.

8voto

David Dimalanta Punkte 538

Ich sehe, dass Sie die Antwort von @providence akzeptiert haben. Nur für den Fall, dass Sie den Handler auch verwenden können! Machen Sie zuerst die int-Felder.

    private static final int SHOW_LOG = 1;
    private static final int HIDE_LOG = 0;

Als nächstes erstellen Sie eine Handler-Instanz als Feld.

    //TODO __________[ Handler ]__________
    @SuppressLint("HandlerLeak")
    protected Handler handler = new Handler()
    {
        @Override
        public void handleMessage(Message msg)
        {
            // Put code here...

            // Set a switch statement to toggle it on or off.
            switch(msg.what)
            {
            case SHOW_LOG:
            {
                ads.setVisibility(View.VISIBLE);
                break;
            }
            case HIDE_LOG:
            {
                ads.setVisibility(View.GONE);
                break;
            }
            }
        }
    };

Erstellen Sie eine Methode.

//TODO __________[ Callbacks ]__________
@Override
public void showHandler(boolean show)
{
    handler.sendEmptyMessage(show ? SHOW_LOG : HIDE_LOG);
}

Setzen Sie dies schließlich unter onCreate() Methode.

showHandler(true);

8voto

Hamid Punkte 1364

Verwenden Sie diesen Code, und Sie müssen nicht runOnUiThread Funktion:

private Handler handler;
private Runnable handlerTask;

void StartTimer(){
    handler = new Handler();   
    handlerTask = new Runnable()
    {
        @Override 
        public void run() { 
            // do something  
            textView.setText("some text");
            handler.postDelayed(handlerTask, 1000);    
        }
    };
    handlerTask.run();
}

7voto

Für alle, die Fragment verwenden:

(context as Activity).runOnUiThread {
    //TODO
}

7voto

topher217 Punkte 968

Für eine Einzeiler-Version der runOnUiThread() Ansatz können Sie eine Lambda-Funktion verwenden, d. h.:

runOnUiThread(() -> doStuff(Object, myValue));

wobei doStuff() kann eine Methode darstellen, mit der der Wert eines UI-Objekts geändert werden kann (Text setzen, Farben ändern usw.).

Ich finde dies viel übersichtlicher, wenn ich versuche, mehrere UI-Objekte zu aktualisieren, ohne eine 6-zeilige Runnable-Definition für jedes Objekt zu benötigen, wie es in die am meisten hochgestimmte Antwort was keineswegs falsch ist, es nimmt nur viel mehr Platz ein und ist meiner Meinung nach weniger lesbar.

Also dies:

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        doStuff(myTextView, "myNewText");
    }
});

kann dies werden:

runOnUiThread(() -> doStuff(myTextView, "myNewText"));

wo die Definition von doStuff an anderer Stelle liegt.

Oder wenn Sie nicht so verallgemeinerungsfähig sein müssen, sondern nur den Text eines TextView-Objekts festlegen wollen:

runOnUiThread(() -> myTextView.setText("myNewText"));

7voto

Sankar Behera Punkte 791

Ich benutze Handler con Looper.getMainLooper() . Bei mir hat es gut funktioniert.

    Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
              // Any UI task, example
              textView.setText("your text");
        }
    };
    handler.sendEmptyMessage(1);

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X