Ärger mit den Threads

  • 5 Antworten
  • Neuester Beitrag
Diskutiere Ärger mit den Threads im Android App Entwicklung im Bereich Betriebssysteme & Apps.
ui_3k1

ui_3k1

Gesperrt
Guten Morgen,

ich wollte mich mal erkundigen, ob jemand hilfreiches Material zum Thema "Threads" kennt.
Irgendwie hat die Sache bei mir immer noch nicht "klick" gemacht.

Hintergrund: Bei mir läuft der Thread um eine Game-Loop laufen zu lassen - wenn das Spiel beendet ist (davor bestand eine online Verbindung zu einem Google-Play-Raum), rufe ich "leaveRoom" auf und es kommt immer der selbe Fehler. Die Methode leaveRoom habe ich an verschiedenen Stellen versucht aufzurufen - leider bringt das keinen Unterschied - auch nicht was die Fehlermeldung angeht.

Der Auszug aus Logcat bringt mich dementsprechend auch nicht großartig weiter...
Code:
06-14 06:15:01.140: D/MyMain(16723): Leaving room.
06-14 06:15:01.145: W/dalvikvm(16723): threadid=11: thread exiting with uncaught exception (group=0x41994ce0)
06-14 06:15:01.150: E/AndroidRuntime(16723): FATAL EXCEPTION: Thread-1608
06-14 06:15:01.150: E/AndroidRuntime(16723): Process: de.clevercomputing.shootank, PID: 16723
06-14 06:15:01.150: E/AndroidRuntime(16723): android.view.ViewRootImpl$CalledFromWrongThreadException:[COLOR=Red] Only the original thread that created a view hierarchy can touch its views.[/COLOR] [B]<- hier vermute ich den Fehler[/B]
06-14 06:15:01.150: E/AndroidRuntime(16723):     at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6094)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:824)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at android.view.View.requestLayout(View.java:16438)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at android.view.View.setLayoutParams(View.java:10585)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at android.view.WindowManagerGlobal.updateViewLayout(WindowManagerGlobal.java:282)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at android.view.WindowManagerImpl.updateViewLayout(WindowManagerImpl.java:74)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at android.app.Activity.onWindowAttributesChanged(Activity.java:2329)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at android.view.Window.setFlags(Window.java:759)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at android.view.Window.clearFlags(Window.java:725)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at de.clevercomputing.shootank.MyMain.stopKeepingScreenOn(MyMain.java:702)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at de.clevercomputing.shootank.MyMain.leaveRoom(MyMain.java:359)
06-14 06:15:01.150: E/AndroidRuntime(16723):     at de.clevercomputing.shootank.GameLoop.run(GameLoop.java:66)
Was mich dann wieder zu folgendem Link brachte: java - Android - ViewRootImpl$CalledFromWrongThreadException - Stack Overflow

Trotzdem sehe ich nicht wirklich eine Parallele zu meinem Problem :-/
Irgendwie fehlts mir da an Background und deswegen suche ich nach geeigneter Lektüre. :smile:
 
Zuletzt bearbeitet:
markus.tullius

markus.tullius

Experte
In der Meldung Only the original thread that created a view hierarchy can touch its views steckt schon die Antwort.

Du brauchst ein UIThread (bzw. Activity, Fragment, Dialog usw.). Denn nur in diesen kann Layout eingebunden und verändert werden.
Du veränderst aber von einen anderen Thread dein Layout, das führt zu dem Fehler.

Du brauchst ein Handler oder die Methode runOnUiThread() oder die Annotations @UIThread.
 
ui_3k1

ui_3k1

Gesperrt
Vielen Dank für die Info.

Habe mich mal etwas in das Thema eingelesen, werde aber noch ein bisschen recherchieren muessen...
Mein aktueller Erkenntnisstand:
In Android wird alles von dem (Main) UI-Thread gesteuert, der automatisch (mit)angelegt ist, sobald ein Lebenszyklus (sprich eine Activity) besteht.

Wenn ich jetzt mit meiner Game-Loop einen neuen Thread anlege, wird der bestehende UI-Thread ueberschrieben, bzw. verliert vorerst seine Bedeutung, da nicht mehr im Vordergrund. Kann man das so sagen?
Nun muss ich schauen, dass für "nach Abarbeitung der Game-Loop", der UI-Thread wieder "übernimmt" - right?

Ein bisschen fehlt mir noch der Überblick, obwohl ich bei meinem Programm nichts "gecopy-pastet" habe (außer natürlich die Implementierung der Play-Services)...
Derzeit ist mein Projekt generell etwas verworren.

Der Multiplayer wird direkt über die Main gestartet, in dem ein neues GameView-Objekt erzeugt wird, während der Single-Player über eine separate Game-Activity verfügt und dort GameView anlegt. Die Loop kommt dann schließlich durch einen Konstruktor in der GameView zustande. Generell kann man sagen, dass in meiner Game View die ganze Logik zusammen läuft - ist das von der Implementierung so sinnvoll, oder gibt es vielleicht andere (übersichtlichere) Möglichkeiten.
 
DieGoldeneMitte

DieGoldeneMitte

Experte
"verliert an bedeutung" ist echt seltsam formuliert. Das ist und bleibt der Thread, der entscheidet, ob deine UI auf Eingaben reagiert, also dem User das Gefühl gibt, dass das Gerät "läuft". Dieser Thread wird nicht "überschrieben", der bleibt immer da. Er ist und bleibt der "Vordergrund-Thread".

Nebenthreads oder bessere "weitere Threads" laufen gleichzeitig
[*] und völlig unabhängig. Es gibt Sachen, die nur in diesen Nebenthreads stattfinden dürfen (wie Network IO oder länger laufende Rechnungen) und es gibt Sachen, die nur im UI Thread stattfinden dürfen (UI Elemente verändern).

Wenn Du eine GameLoop als eigenen Thread hast, übernimmt nicht der UI Thread nachher die Kontrolle, sondern der GameThread liefert Daten an den UI Thread (runOnUIThread,onPostExecute,whatever), die dieser dann (wenn er es für richtig hält) rendert.



[*] im Prinzip :D
 
A

amfa

Experte
Hier ist mal ein ganz simples Thread Beispiel:
Code:
package de.amfa.testproject;

public class ThreadExample {

	public static void main(String[] args) {

		Thread thread1 = new MyThread1();

		thread1.start();
		for (int i = 0; i < 50; i++) {
			printOut("MainThread", i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	private static class MyThread1 extends Thread {
		@Override
		public void run() {
			for (int i = 0; i < 50; i++) {
				printOut("MyThread1", i);
				try {
					sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	private static void printOut(String threadName, int count) {
		System.out.println(threadName + ": " + count);
	}
}
Wenn du das mal ausführst wirst du sehen, das beide Schleifen quasi gleichzeitig laufen und wenn beide Threads normal laufen sollte der eine exakt doppelt so schnell zählen wie der andere.

Die Ausgabe sieht dann so aus:
Code:
MainThread: 0
MyThread1: 0
MainThread: 1
MyThread1: 1
MainThread: 2
MainThread: 3
MyThread1: 2
MainThread: 4
MainThread: 5
MyThread1: 3
MainThread: 6
MainThread: 7
MyThread1: 4
MainThread: 8
MainThread: 9
MyThread1: 5
MainThread: 10
In Android kannste dir das so vorstellen, dass der MainThread der UIThread ist.
Wenn du jetzt in dem anderen Thread (hier myThread1), der ja gleichzeitig läuft versuchst auf die UI zuzugreifen bekommst du den Fehler aus deiner Exception.
Nur der UI Thread (in meinem Beispiel der mainThread) darf Änderungen an der UI machen.

Du siehst auch, dass beide auf die gleiche Methode der Klasse zugreifen.
Wenn wir jetzt in Android wären und in dieser Methode würde irgendwas am UI geändert würde der MyThread beim aufruf dieser Methode auch den Fehler werfen.
 
ui_3k1

ui_3k1

Gesperrt
Super, ich danke auch euch beiden.
Habs hinbekommen. War am Ende doch recht unkompliziert...

Ich weiß nur nicht ob die Implementierung so gute Praxis ist:
In meinem GameLoop Konstruktor habe ich
this.context = gameView.getContext();
hinzugefuegt.

spaeter, im eigentlichen Thread, pruefe ich ob das Spiel zu Ende, wobei ich auf eine Singleton Referenz zurueckgreife ist und rufe dann wieder meine Main auf (Spielvariablen werden auch zurueck gesetzt).
Insgesamt sieht jetzt meine Loop so aus:

Code:
@Override
    public void run() {
        long TPS = 1000 / FPS;
        long startTime, sleepTime;
        long syncTime = System.currentTimeMillis();
        final long fixedStartTime = System.currentTimeMillis(); // Verzoegert erste Sendung

        // Game-Loop Start
        while (keepPlaying) {
            Canvas canvas = null;
            startTime = System.currentTimeMillis();
            try {
                canvas = gameView.getHolder().lockCanvas();
                synchronized (gameView.getHolder()) {
                    gameView.onDraw(canvas);

                }
            } finally {
                if (canvas != null) {
                    gameView.getHolder().unlockCanvasAndPost(canvas);
                    // wenn Multiplayer UND Signal zum Update UND Startveroegerung abgelaufen
                    // -> Daten synchronisieren 
                    if (isMultiplayer) {
                        if ((System.currentTimeMillis() - syncTime) > updateRate
                                && fixedStartTime + startDelay < System.currentTimeMillis()) {
                            myMainInstance.broadcastData();
                              gameView.getSprite(GameView.PLAYER_1).setSpriteShot(GameView.FALSE); //  setzt beide Sprite zurueck auf
                             gameView.getSprite(GameView.PLAYER_2).setSpriteShot(GameView.FALSE); //  Status "nicht geschossen = 0"

                            syncTime = System.currentTimeMillis(); // aktualisieren
                        }

                    }
                }
            }
            sleepTime = TPS - (System.currentTimeMillis() - startTime);
            try {
                if (gameView.isGameOver() != 0) { // pruefe auf Spielende
                    switch (gameView.isGameOver()) {
                        case 1 : // Spieler 1 hat gewonnen

                            break;
                        case 2 : // Spieler 2 hat gewonnen

                            break;
                        default :
                            break;
                    }
                    
                    if (isMultiplayer) {
                        // hier Spezialfall fuer "Raum"
                    }
                    Intent intent = new Intent(context, MyMain.class);
                    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                    context.startActivity(intent);

                } // Ende der GameOver Bedingung

                if (sleepTime > 0)
                    sleep(sleepTime);
                else
                    sleep(10);
            } catch (Exception e) {
            }
        } // Loop Ende
Ein richtiger Grund warum das so nicht "sauber" sein soll, fällt mir zwar nicht ein, aber mein (C++)-Bauchgefühl sagt mir, dass der Code eher zusammen gebastelt ist.
Kann es später zu Problemen wegen der Singleton Instanz geben? Bis dato funktioniert alles wunderbar... :)