it-swarm.com.de

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

Ich habe einen einfachen Musikplayer in Android gebaut. Die Ansicht für jedes Lied enthält eine SeekBar, die folgendermaßen implementiert ist: 

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 Fortschritts des Songs zählt. Also setze ich eine TextView in das Layout, besorge es mit findViewById() in onCreate() und stelle dies in 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 gibt mir die Ausnahme:

Android.view.ViewRoot $ CalledFromWrongThreadException: Nur der ursprüngliche Thread, der eine Ansichtshierarchie erstellt hat, kann seine Ansichten berühren.

Aber ich mache hier im Grunde dasselbe wie mit SeekBar - die Ansicht in onCreate erstellen, dann in run() anfassen - und das gibt mir diese Beschwerde nicht.

782
herpderp

Sie müssen den Teil der Hintergrundaufgabe, der die Benutzeroberfläche aktualisiert, in den Haupt-Thread verschieben. Dafür gibt es einen einfachen 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 ausgeführt wird, und fügen Sie den Code ein, der alle Aktualisierungen in der Mitte des Blocks implementiert. Fügen Sie nur die kleinstmögliche Menge an Code ein, andernfalls beginnen Sie, den Zweck des Hintergrundthreads zu umgehen.

1646
providence

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

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();
121

Meine Lösung dazu:

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 Hintergrundthread auf.

49
Angelo Angeles

Normalerweise muss jede Aktion, die die Benutzeroberfläche betrifft, im Haupt- oder UI-Thread ausgeführt werden. Dies ist die Aktion, in der onCreate() und die Ereignisbehandlung ausgeführt werden. Eine Möglichkeit, um sicher zu gehen, ist die Verwendung von runOnUiThread () . Eine andere Möglichkeit ist die Verwendung von Handlers.

ProgressBar.setProgress() hat einen Mechanismus, für den es immer im Haupt-Thread ausgeführt wird. Deshalb hat es funktioniert.

Siehe schmerzloses Einfädeln .

23
bigstones

Ich war in dieser Situation, habe aber mit dem Handler-Objekt eine Lösung gefunden.

In meinem Fall möchte ich einen ProgressDialog mit dem Observer-Muster ..__ aktualisieren. Meine Ansicht implementiert Observer und überschreibt die Update-Methode.

Also, mein Haupt-Thread erstellt die Ansicht und ein anderer Thread ruft die Aktualisierungsmethode auf, mit der ProgressDialop und ... aktualisiert werden:

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

Es ist möglich, das Problem mit dem Handler-Objekt zu lösen.

Nachfolgend verschiedene Teile meines Codes:

public class ViewExecution extends Activity implements Observer{

    static final int PROGRESS_DIALOG = 0;
    ProgressDialog progressDialog;
    int currentNumber;

    public void onCreate(Bundle savedInstanceState) {

        currentNumber = 0;
        final Button launchPolicyButton =  ((Button) this.findViewById(R.id.launchButton));
        launchPolicyButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                showDialog(PROGRESS_DIALOG);
            }
        });
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        switch(id) {
        case PROGRESS_DIALOG:
            progressDialog = new ProgressDialog(this);
            progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            progressDialog.setMessage("Loading");
            progressDialog.setCancelable(true);
            return progressDialog;
        default:
            return null;
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
        switch(id) {
        case PROGRESS_DIALOG:
            progressDialog.setProgress(0);
        }

    }

    // Define the Handler that receives messages from the thread and update the progress
    final Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            int current = msg.arg1;
            progressDialog.setProgress(current);
            if (current >= 100){
                removeDialog (PROGRESS_DIALOG);
            }
        }
    };

    // The method called by the observer (the second thread)
    @Override
    public void update(Observable obs, Object arg1) {

        Message msg = handler.obtainMessage();
        msg.arg1 = ++currentPluginNumber;
        handler.sendMessage(msg);
    }
}

Diese Erklärung finden Sie auf dieser Seite und Sie müssen den "Beispiel-ProgressDialog mit einem zweiten Thread" lesen.

19
Jonathan

Ich sehe, dass Sie die Antwort von @ Provence akzeptiert haben. Für den Fall, dass Sie auch den Handler benutzen können! Zuerst machen Sie die int Felder.

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

Als Nächstes erstellen Sie eine Handlerinstanz 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;
            }
            }
        }
    };

Machen Sie eine Methode.

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

Zum Schluss setzen Sie diese auf onCreate()-Methode.

showHandler(true);
6
David Dimalanta

Ich hatte ein ähnliches Problem und meine Lösung ist hässlich, aber es funktioniert:

void showCode() {
    hideRegisterMessage(); // Hides view 
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            showRegisterMessage(); // Shows view
        }
    }, 3000); // After 3 seconds
}
6
Błażej

Ich verwende Handler mit Looper.getMainLooper(). Es hat gut für mich funktioniert.

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

Mit Handler können Sie die Ansicht löschen, ohne den Haupt-UI-Thread zu stören. Hier sehen Sie Beispielcode

new Handler(Looper.getMainLooper()).post(new Runnable() {
                                                        @Override
                                                        public void run() {
                                                           //do stuff like remove view etc
                                                            adapter.remove(selecteditem);
                                                        }
                                                    });
5
Bilal Mustafa

Dies löst explizit einen Fehler aus. Es sagt, welcher Thread eine Ansicht erstellt hat, nur die Ansichten können berührt werden. Dies liegt daran, dass sich die erstellte Ansicht innerhalb des Bereichs des Threads befindet. Die Ansichtserstellung (GUI) erfolgt im UI-Hauptthread. Daher verwenden Sie immer den UI-Thread, um auf diese Methoden zuzugreifen. 

 Enter image description here

Im obigen Bild befindet sich die Fortschrittsvariable innerhalb des Bereichs des UI-Threads. Daher kann nur der UI-Thread auf diese Variable zugreifen. Hier greifen Sie über einen neuen Thread () auf den Fortschritt zu, weshalb Sie einen Fehler erhalten haben. 

4
Uddhav Gautam

Verwenden Sie diesen Code und keine 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();
}
3
Hamid

Bei Verwendung von AsyncTask Aktualisieren Sie die Benutzeroberfläche in der onPostExecute -Methode

    @Override
    protected void onPostExecute(String s) {
   // Update UI here

     }
2
Deepak Kataria

Dies geschah bei mir, als ich eine Änderung der Benutzeroberfläche von einer doInBackground von Asynctask forderte, anstatt onPostExecute zu verwenden.

Der Umgang mit der Benutzeroberfläche in onPostExecute hat mein Problem gelöst.

2
Jonathan

Wenn Sie die runOnUiThread-API nicht verwenden möchten, können Sie AsynTask für die Vorgänge implementieren, deren Ausführung einige Sekunden in Anspruch nimmt. In diesem Fall müssen Sie jedoch auch nach der Bearbeitung Ihrer Arbeit in doinBackground() die fertige Ansicht in onPostExecute() zurückgeben. Bei der Android-Implementierung kann nur der Haupt-UI-Thread mit Ansichten interagieren.

1
Sam

Dies ist der Stack-Trace der erwähnten Ausnahme

        at Android.view.ViewRootImpl.checkThread(ViewRootImpl.Java:6149)
        at Android.view.ViewRootImpl.requestLayout(ViewRootImpl.Java:843)
        at Android.view.View.requestLayout(View.Java:16474)
        at Android.view.View.requestLayout(View.Java:16474)
        at Android.view.View.requestLayout(View.Java:16474)
        at Android.view.View.requestLayout(View.Java:16474)
        at Android.widget.RelativeLayout.requestLayout(RelativeLayout.Java:352)
        at Android.view.View.requestLayout(View.Java:16474)
        at Android.widget.RelativeLayout.requestLayout(RelativeLayout.Java:352)
        at Android.view.View.setFlags(View.Java:8938)
        at Android.view.View.setVisibility(View.Java:6066)

Wenn Sie also Dig besuchen, werden Sie wissen

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

Wo mThread wird im Konstruktor wie folgt initialisiert

mThread = Thread.currentThread();

Alles, was ich damit sagen will, als wir eine bestimmte Ansicht erstellt haben, haben wir sie in UI-Thread erstellt und versuchen später, sie in einem Worker-Thread zu ändern.

Wir können es über den folgenden Code-Ausschnitt überprüfen

Thread.currentThread().getName()

wenn wir das Layout aufblasen und später, wo Sie eine Ausnahme bekommen.

1
Amit Yadav

Ich arbeitete mit einer Klasse, die keinen Hinweis auf den Kontext enthielt. Daher war es mir nicht möglich, runOnUIThread(); zu verwenden, ich verwendete view.post(); und es wurde gelöst.

timer.scheduleAtFixedRate(new TimerTask() {

    @Override
    public void run() {
        final int currentPosition = mediaPlayer.getCurrentPosition();
        audioMessage.seekBar.setProgress(currentPosition / 1000);
        audioMessage.tvPlayDuration.post(new Runnable() {
            @Override
            public void run() {
                audioMessage.tvPlayDuration.setText(ChatDateTimeFormatter.getDuration(currentPosition));
            }
        });
    }
}, 0, 1000);
1
Ifta

In meinem Fall ruft der Anrufer zu oft in kurzer Zeit diesen Fehler auf, ich setze einfach die elpased-Zeitprüfung ein, um nichts zu tun, wenn er zu kurz ist, z.B. ignorieren, wenn die Funktion weniger als 0,5 Sekunden aufgerufen wird:

    private long mLastClickTime = 0;

    public boolean foo() {
        if ( (SystemClock.elapsedRealtime() - mLastClickTime) < 500) {
            return false;
        }
        mLastClickTime = SystemClock.elapsedRealtime();

        //... do ui update
    }
0
林果皞

Für die Leute, die in Kotlin kämpfen, funktioniert das so:

lateinit var runnable: Runnable //global variable

 runOnUiThread { //Lambda
            runnable = Runnable {

                //do something here

                runDelayedHandler(5000)
            }
        }

        runnable.run()

 //you need to keep the handler outside the runnable body to work in kotlin
 fun runDelayedHandler(timeToWait: Long) {

        //Keep it running
        val handler = Handler()
        handler.postDelayed(runnable, timeToWait)
    }
0
Tarun Kumar

Für mich bestand das Problem darin, dass ich onProgressUpdate() explizit von meinem Code aus anrief. Das sollte nicht gemacht werden. Ich rief stattdessen publishProgress() an und das hat den Fehler behoben.

0
mindreader

Gelöst: Setzen Sie diese Methode einfach in doInBackround Class ... und übergeben Sie die Nachricht

public void setProgressText(final String progressText){
        Handler handler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // Any UI task, example
                progressDialog.setMessage(progressText);
            }
        };
        handler.sendEmptyMessage(1);

    }
0
Kaushal Sachan

Wenn Sie einfach die Aufhebung der Funktion (Aufruf der Funktion "Repaint/Redraw") von Ihrem Thread außerhalb der Benutzeroberfläche aus durchführen möchten, verwenden Sie postInvalidate ().

myView.postInvalidate();

Dadurch wird eine Ungültigkeitsanforderung im UI-Thread bereitgestellt.

Weitere Informationen: what-do-postinvalidate-do

0
Nalin

Ich hatte ein ähnliches Problem und keine der oben genannten Methoden funktionierte für mich. Am Ende war das der Trick für mich:

Device.BeginInvokeOnMainThread(() =>
    {
        myMethod();
    });

Ich habe dieses Juwel gefunden hier .

0
Hagbard

In meinem Fall Ich habe EditText im Adapter und ist bereits im UI-Thread. Wenn diese Aktivität geladen wird, stürzt sie jedoch mit diesem Fehler ab.

Meine Lösung ist, dass ich <requestFocus /> aus EditText in XML entfernen muss.

0
Sruit A.Suk