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.

2302voto

providence Punkte 28095

Sie müssen den Teil der Hintergrundaufgabe, der die Benutzeroberfläche aktualisiert, in den Hauptthread verschieben. Hierfür gibt es ein einfaches Stück Code:

runOnUiThread(new Runnable() {

    @Override
    public void run() {

        // Stuff that updates the UI

    }
});

Dokumentation für Activity.runOnUiThread .

Verschachteln Sie dies einfach in der Methode, die im Hintergrund läuft, und fügen Sie dann den Code, der alle Aktualisierungen durchführt, in der Mitte des Blocks ein. Fügen Sie nur die kleinstmögliche Menge an Code ein, da Sie sonst den Zweck des Hintergrund-Threads zunichte machen.

162voto

Günay Gültekin Punkte 3869

Ich habe das Problem gelöst, indem ich runOnUiThread( new Runnable(){ .. innerhalb run() :

thread = new Thread(){
        @Override
        public void run() {
            try {
                synchronized (this) {
                    wait(5000);

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            dbloadingInfo.setVisibility(View.VISIBLE);
                            bar.setVisibility(View.INVISIBLE);
                            loadingText.setVisibility(View.INVISIBLE);
                        }
                    });

                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Intent mainActivity = new Intent(getApplicationContext(),MainActivity.class);
            startActivity(mainActivity);
        };
    };  
    thread.start();

89voto

Angelo Angeles Punkte 951

Meine Lösung für dieses Problem:

private void setText(final TextView text,final String value){
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            text.setText(value);
        }
    });
}

Rufen Sie diese Methode in einem Hintergrund-Thread auf.

52voto

KenIchi Punkte 933

Kotlin-Coroutinen können Ihren Code prägnanter und lesbarer machen, etwa so:

MainScope().launch {
    withContext(Dispatchers.Default) {
        //TODO("Background processing...")
    }
    TODO("Update UI here!")
}

Oder andersherum:

GlobalScope.launch {
    //TODO("Background processing...")
    withContext(Dispatchers.Main) {
        // TODO("Update UI here!")
    }
    TODO("Continue background processing...")
}

34voto

bigstones Punkte 14857

Normalerweise muss jede Aktion, die die Benutzeroberfläche betrifft, im Haupt- oder UI-Thread ausgeführt werden, d. h. in demjenigen, in dem onCreate() und Ereignisbehandlung ausgeführt werden. Eine Möglichkeit, dies zu gewährleisten, ist die Verwendung von runOnUiThread() eine andere ist die Verwendung von Handlern.

ProgressBar.setProgress() hat einen Mechanismus, der dafür sorgt, dass es immer im Haupt-Thread ausgeführt wird, deshalb hat es funktioniert.

Voir Schmerzfreies Einfädeln .

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