Thread mit Endlosschleife langsam (auf Nexus 4)

  • 8 Antworten
  • Letztes Antwortdatum
R

Rul3r

Neues Mitglied
0
Hi,

ich entwickle gerade ein kleines Spiel auf OpenGL-Basis. Nun bin ich an die Eingabe gekommen. Ich will, dass solange man den Bildschirm berührt etwas gemacht wird. Dabei bin ich aber auf ein Problem gestoßen.
Ich dachte ich löse das wie folgt:

PHP:
@Override
    public boolean onTouchEvent(MotionEvent event) {
    
        touchX = event.getX();
        touchY = event.getY();

        move_action = event.getAction();

        if (move_action == MotionEvent.ACTION_DOWN) {
            
            new Thread(new Runnable() {

                @Override
                public void run() {
                   
                    while (true) {

                         game.setInputCoordinates(touchX, touchY);

                       if (move_action == MotionEvent.ACTION_UP) {
                         
                            break;
                        }

                    }

                }
            }).start();

        }
       
        return super.onTouchEvent(event);
    }
Dieser Code steht in in der MainActivity. Solange man mit dem Finger auf dem Bildschirm ist, werden die Koordinaten der Berührposition an meine Spielklasse übergeben. Wenn man den Finger wieder vom Bildschirm nimmt, wird die Endlosschleife abgebrochen und der Thread beendet.

Auf meinem Galaxy S3 läuft das auch so wie es soll und frisst auch nicht zu viel Leistung. Auf einem Nexus 4 fallen die FPS von 60 auf 30 sobald man den Bildschirm berührt. Nehm ich den Thread raus, dann läuft es auch auf dem Neux 4 flüssig.


Ich vermute mal es liegt an der Endlosschleife im Thread :laugh:


Jetzt weiß ich allerdings nicht warum die FPS auf dem Nexus 4 so einbrechen, könnte mir aber vorstellen, dass es auf schwächeren handys noch schlimmer ist. Ich bräuchte also eine Alternative zu der Endlosschleife.


Den Thread 20ms schlafen legen und dann weiterlaufen lassen, bringt etwas mehr Leistung, allerdings könnten mir dann Eingabekoordinaten verloren gehen, was vielelicht zu unerwarteten Dingen führen könnte.


Gibt es eine andere Möglichkeit solange man den Bildschirm beührt etwas zu machen und dies dann zu stoppen, wenn man den Bildschirm nicht mehr berührt?



Über Tipps und Hilfestellungen wäre ich dankbar
 
Hi,

danke für die Antwort. Ich hab das gerade mal wie folgt umgesetzt:

PHP:
@Override
    public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub
        touchX = event.getX();
        touchY = event.getY();
    
        move_action = event.getAction();
        
        while (move_action == MotionEvent.ACTION_DOWN) {
            move_action = event.getAction();
            
            game.setInputCoordinates(touchX, touchY);
            
            if (move_action == MotionEvent.ACTION_UP) {
                break;
            }
        }
        
        
        return false;
    }

Irgendwo schein ich da aber n Denkfehler drin zu haben. Die Schleife wird nie abgebrochen. move_action scheint nie neugesetzt zu werden und immer auf ACTION_DOWN zu verbleiben
 
Hi,

naja, dein MotionEvent in der Methode ist ja auch eine lokale Variable. Die Aktualisierung der Variable würde erst durch einen erneuten Aufruf der Methode passieren. Das passiert aber nicht, da die while-Schleife nicht verlassen wird.


Schau Dir diesen Code mal an:


android: move a view on touch move (ACTION_MOVE) - Stack Overflow

LG Jan
 
  • Danke
Reaktionen: Rul3r
Hi,
nehm einfach die while-Schleife ganz raus.
OnTouch wird automatisch bei jeder Bewegung des Fingers aufgerufen.
Verwende dann einfach:

@Override
public
boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
touchX = event.getX();
touchY = event.getY();

move_action = event.getAction();

if (move_action == MotionEvent.ACTION_MOVE) {
game.setInputCoordinates(touchX, touchY);
return true;
}

return super.onTouch(v, event);
}
 
Hi,

danke für die Antworten, aber die Lösung für das wa sich will, ist leider nicht dabei :sad:

Ohne while-Schleife (switch oder if-Abfrage) läuft das auch (hatte ich am Anfang so, aber es entspricht eben nicht meinen Vorstellungen.

Das OnTouch bzw. OnTouchEvent bei jeder Bewegung aufgerufen wird, ist mir bewusst. Aber ich will eben, dass der Methodenaufruf auch ausgeführt wird, wenn der Figer sich NICHT bewegt. Also: Finger auf den Screen (ACTION_DOWN) und halten, Methode wieder solang aufgerufen bis der Finger wieder weg ist (ACTION_UP).

OnTouchEvent wird einmal aufgerufen, wenn der Finger auf den Screen kommt, solang er sich bewegt (jede Koordinatenänderung ein Aufruf) und wenn der Finger den Screen wieder verlässt.

Nun dachte ich eben, wenn ich weiß, dass der Screen berührt wurde (ACTION_DOWN) und der Finger immer noch auf dem Screen bewegungslos verweilt (kein ACTION_UP-Event), dann starte ich eben eine Schleife, die solang läuft bis ACTION_UP kommt.

Nun blockiert diese Schleife aber den MainThread, dass das MotionEvent nie geändert wird, also die Schleife ewig läuft.

Meine Lösung war dann eben der Thread nur für die Schleife. Dadurch ist der MainThread lauffähig und OnTouchEvent aufrufbar. Es läuft prinzipiell auch so, nur ist diese Lösung auf dem Nexus 4 (und dann bestimmt auch auf vielen anderen Geräten) komischerweise sehr ressourcefressend.

Ist der Eventlistener nicht fähig eine laufende Schleife zu unterbrechen? Also, dass eine Schleife läuft, ein Event vom Listener registriert wird und die Event-Methode ausgeführt wird (was die Schleife dann abbrechen würde), oder ist der Listener an den MainThread gebunden und kann nicht mehr ausgeführt werden (da dieser durch die Schleife blockiert wird), wenn eine Schleife läuft?
 
Hi Rul3r!

Also ich verstehe nicht, warum die Position neu gesetzt werden muss, wenn es keien Positionsveränderung gibt?

Wenn es dir nur darum geht, ander Objecte in OpenGL zu rendern, dann solltest du lieber einen Thread fürs Rendern, unabhängig vom TouchEvent, starten.

Und bei TouchEvents lediglich die neuen Koordinaten setzen, die dann der Renderthread verarbeitet.

Gruß Harry

EDIT:
ich glaube jetzt habe ich verstanden was du willst. Du willst nur neu rendern, wenn der Finger auf dem Schirm ist.
Dann würde ich einen globalen Timer bei ACTION_DOWN starten und bei ACTION_UP beenden.
Ich habe dem Timer ein wiederholungsrate von 30 ms gegeben. Dies kannst du dann ganz nach Wusch ändern. Dies sollte dann auch dein Performance-Problem lösen.
Aber bedenke, je kleiner der Interval desto mehr muss das gerät rendern.

Am besten verwendest du dann folgendes:
@Override public boolean onTouch(View v, MotionEvent event) {
touchX = event.getX();
touchY = event.getY();

move_action = event.getAction();

if (move_action == MotionEvent.ACTION_DOWN) {
startTimer(); return true;
}
else if(move_action == MotionEvent.ACTION_UP) {
stopTimer();
return true;
}
return super.onTouch(v, event); }


private void startTimer() {
if(globalerTimer == null) {
globalerTimer = new Timer();
}
globalerTimer.schedule(new TimerTask() { public void run() {
game.setInputCoordinates(touchX, touchY); } }, 30);
}

private stopTimer() {
if(globalTimer != null) {
globalTimer.cancel();
globalTimer.purge();
}
}
 
Zuletzt bearbeitet:
  • Danke
Reaktionen: Rul3r
Hi,

ja, das geht schon in die richtige Richtung.

Ich hab vielleicht der Methode game.setInputCoordinates(touchX, touchY); einen etwas unpassenden Namen gegeben (überbleibsel von früher) :rolleyes2:

Es sollen nicht nur die Koordinaten gesetzt werden sondern es soll immer ein neues Objekt erstellt werden, wenn sich die Koordinaten ändern (bewegung) oder der Finger dauerhaft den Screen berührt.

Das mit dem Timer hat ziemliche Ähnlichkeit mit meinem Thread (den ich als Notlösung immer 20ms schlafen gelegt hätte). Möglicherweise ist der Timer aber performanceschonender :confused:
Ich werds mal ausprobieren und dann berichten.

EDIT:

Hab das jetzt mal mit nem Timer umgesetzt (mit Timer.schedule() läuft der Timer nur einmal, also Timer.scheduleAtFixedRate())

PHP:
@Override
    public boolean onTouchEvent(MotionEvent event) {

        touchX = event.getX();
        touchY = event.getY();

        move_action = event.getAction();
        if (move_action == MotionEvent.ACTION_DOWN) {

            timer = new Timer();
            timer.scheduleAtFixedRate(new TimerTask() {

                @Override
                public void run() {
                    game.setInputCoordinates(touchX, touchY);

                }
            }, 0, 20);

        } else if (move_action == MotionEvent.ACTION_UP) {
            timer.cancel();

        }

        return true;
    }
Läuft besser als der Thread (ohne sleep(20)) und etwa genauso schnell wie der Thread mit sleep(20). Werd jetzt nochn bisschen mit der Ausführungszeit rumspielen (wei weit ich sie hochsetzten kann bis negative Effekte auftreten)

Danke an Alle :thumbsup:
 
Ja, da hast du natürlich vollkommen recht! Es muss natürlich die Methode Timer.schedulerAtFixedRate(TimerTask, int, int) sein! :thumbup:

Wenn die Bewegungen, die du rendern willst, halb wegs flüssig sein sollen, sollte der Wert nicht größer als 40 sein. Entspricht einer Bildwiederholfrequenz von 25 Bildern pro Sekunde. Ich glaube der einfache Röhrenfernsehen hat eine solche Frequenz. Aber probier ruhig mal etwas rum. ;)
 
Zurück
Oben Unten