1. Nimm jetzt an unserem Uhans - 3. ADVENT - Gewinnspiel teil - Alle Informationen findest Du hier!

Threads absichern

Dieses Thema im Forum "Android App Entwicklung" wurde erstellt von Skolleus, 21.02.2011.

  1. Skolleus, 21.02.2011 #1
    Skolleus

    Skolleus Threadstarter Neuer Benutzer

    Beiträge:
    16
    Erhaltene Danke:
    0
    Registriert seit:
    21.02.2011
    Hi,
    ich bin recht neu in der Android-Programmierung (wie auch hier im Forum) und stehe vor einem kleinen Problem was Threads angeht.
    Ich habe einen Thread, der eigentlich die ganze Zeit Daten (von Socket) lesen soll. Diese werden von ihm ausgewertet und je nach Inhalt wird etwas vom UI-Thread ausgegeben.
    Soll heißen: Es kommen mehrere (sehr viele pro Sekunde) Nachrichten in einem bestimmten Format an, werden ausgewertet und bei Empfang einer oder mehrerer bestimmten Nachrichten (alle paar ms) wird deren enthaltener Wert im Display angezeigt. der Rest wird ignoriert.

    Das Problem dabei: der Lese-Thread arbeitet und lässt nicht zu, dass das Display aktualisiert wird (klar, es kann immer nur ein Thread arbeiten).
    Von Java aus kenne ich das Problem bei mehreren Threads und hätte es z.B mit Semaphoren lösen können. Dummerweise gibt es in Android 2 Einschränkungen.
    1. Nur der UI-Thread kann auf das Display schreibend zugreifen.
    2. Der UI -Thread darf nicht geblockt werden.
    Ich kann ihn also nicht gezielt anhalten, den Lese-Thread ausführen, bei Bedarf diesen wieder anhalten und den UI-Thread etwas anzeigen zu lassen.

    Mein 2. Versuch war AsyncTask.
    Da wird allerdings das Display nur alle paar Sekunden aktualisiert. Es sollte aber innerhalb von ms passieren.
    Hat jemand eine Idee was ich tun kann, um dem Lese-Thread die CPU Zeit zusichern zu können und trotzdem alle paar ms (möglichst zeitnah nach Erhalt der entsprechenden Nachricht) das Display aktualisiert zu kriegen?
     
  2. ko5tik, 21.02.2011 #2
    ko5tik

    ko5tik Android-Experte

    Beiträge:
    620
    Erhaltene Danke:
    40
    Registriert seit:
    14.10.2009
    Phone:
    HTC Hero
    Wenn du auf die Widgets zugreifen willst, geht es nur von dem UI-Thread
    ( Also - async task ore runOnUiThread() ) - allerdings hast du da kein Einfluss auf
    Laufzeitverhalten ( es wird irgendwann ausgeführt, wenn Update mal fällig ist )

    Wenn du Zeitnahe Updates willst, must due mit der Surface Arbeiten und selber drauf zeichnen.

    PS: 2-3 ms sind eindeutig zu schnell. Kein Benutzer wird deinen EInsatz würdigen können. 20 ms
    reichen vollkommen aus
     
    Zuletzt bearbeitet: 21.02.2011
    Skolleus bedankt sich.
  3. Skolleus, 21.02.2011 #3
    Skolleus

    Skolleus Threadstarter Neuer Benutzer

    Beiträge:
    16
    Erhaltene Danke:
    0
    Registriert seit:
    21.02.2011
    Ich hab keine Widgets sondern zeichne selbst (ist ein Bitmap und ein paar Standardformen). Wie man das kombiniert wäre eine Frage für die nächsten Wochen *g.
    Die Zeitangabe bezieht sich auf die einkommenden Nachrichten. Nicht auf die Anzeige Darauf habe ich auch keinen Einfluss. Momentan liegt die dadurch entstehende Updaterate allerdings bei >5 Sekunden. Absolut unbrauchbar.
    20+ Updates pro Sekunde müssten es schon sein. Also 50ms oder besser.
     
  4. ko5tik, 21.02.2011 #4
    ko5tik

    ko5tik Android-Experte

    Beiträge:
    620
    Erhaltene Danke:
    40
    Registriert seit:
    14.10.2009
    Phone:
    HTC Hero
    Dann ist die SurfaceView deine Wahl - schau einfach den LunarLander Sample an
     
  5. Skolleus, 21.02.2011 #5
    Skolleus

    Skolleus Threadstarter Neuer Benutzer

    Beiträge:
    16
    Erhaltene Danke:
    0
    Registriert seit:
    21.02.2011
    OK werd ich machen. Danke schonmal.
     
  6. Skolleus, 25.02.2011 #6
    Skolleus

    Skolleus Threadstarter Neuer Benutzer

    Beiträge:
    16
    Erhaltene Danke:
    0
    Registriert seit:
    21.02.2011
    Also nachdem ich heute mal dazu kam mir das anzuschauen, weiß ich nicht wie mir das bei meinem Problem helfen soll.
    Das Ding ruft auch nur invalidate auf und wartet dann darauf, dass irgendwann mal das Display aktualisiert wird.

    Es muss doch irgendwie möglich sein kontinuierlich Daten von einem Socket zu lesen, zu verarbeiten und das Ergebnis flüssig (also mindestens 20Bilder pro Sekunde) auf dem Bildschirm darzustellen.

    Ich habe eine Klasse die einen Socket öffnet und Daten (es kommt alle 50ms ein Set mit vielen Nachrichten) liest: das passiert in einem eigenen Thread
    Die gibt das Ganze an eine andere Klasse (Parser) die die Daten bearbeitet und alles wichtige in ein Datenobjekt schreibt: das passiert im UI Thread (momentan noch über runOnUiThread)
    Jetzt soll anhand dieser Daten etwas flüssig auf dem Bildschirm ausgegeben werden.

    Als Beispiel kann man sich ein analoges Messgerät vorstellen, bei dem die Werte über eine Socketverbindung reinkommen.
     
    Zuletzt bearbeitet: 25.02.2011
  7. RED-BARON, 25.02.2011 #7
    RED-BARON

    RED-BARON Android-Hilfe.de Mitglied

    Beiträge:
    146
    Erhaltene Danke:
    19
    Registriert seit:
    06.10.2009
    hm, mein Socket z.B. verarbeitet auch Daten schnell, den "Fortschritt"
    gebe ich über einen EventHandler aus dem WorkerThread an die UI weiter
    und bis jetzt konnte ich in meinem Anwendungsfall keine merklichen
    Verzögerungen feststellen. Der "Fortschritt" wird aber auch nur in einer
    Textbox ausgegeben, also nix mit Grafik zeichen - da habe ich keine Erfahrungen mit.

    Bekommst Du denn die 20 Bilder pro Sekunde animiert *ohne* Abhängigkeit
    von deinem WorkerThread ?
     
  8. Skolleus, 25.02.2011 #8
    Skolleus

    Skolleus Threadstarter Neuer Benutzer

    Beiträge:
    16
    Erhaltene Danke:
    0
    Registriert seit:
    21.02.2011
    Ich hab jetzt 2 verschiedene Modis.
    Es wird alle 50ms ein Paket mit sehr vielen Nachrichten gesendet und zeilenweise ausgelesen.
    1. Jede Nachricht kommt in eigener Zeile.
    2. Das komplette Paket besteht aus einer einzigen Zeile.

    zu 1.
    Wenn ich einen TimerTask mit 1ms Delay benutze, ist die Bewegung zwar flüssig, aber ich kann nur 50 Nachrichten pro Paket abarbeiten.
    Wenn ich kein delay eintrage, wird die Menge der Nachrichten zwar nur von der Prozessgeschwindigkeit begrenzt, aber das Display wird nur alle 5+ Sekunden aktualisiert.

    zu 2. Das muss ich am Montag noch erarbeiten.. noch nicht getestet.
     
  9. ko5tik, 25.02.2011 #9
    ko5tik

    ko5tik Android-Experte

    Beiträge:
    620
    Erhaltene Danke:
    40
    Registriert seit:
    14.10.2009
    Phone:
    HTC Hero
    folgendes sollst du von deiner Update-Thread machen:

    Code:
        /**
         * draw back buffer on the screen.
         */
        public void updateFrontBuffer() {
            //  draw on screen out back buffer
            Canvas c = null;
            try {
                // TODO: investigate whether to guard here,  or prevent this globally
                // as race confition
                c = holder.lockCanvas(null);
                // synchronized (mSurfaceHolder) {
                c.drawBitmap(fieldBitmap, 0, 0, backgroundPaint);
                // }
            } finally {
                // do this in a finally so that if an exception is thrown
                // during the above, we don't leave the Surface in an
                // inconsistent state
                if (c != null) {
                    holder.unlockCanvasAndPost(c);
                }
            }// end finally block
        }
    
    Das updated das Bild in deiner SurfaceView sofort (dein Front-Buffer ) Und deine Bilder/Texte/Linien/was auch immer zeichnest du in einen BItmap ( BackBuffer)

    Es ist aktuelles code von meinen Spiel:
    https://market.android.com/details?id=de.pribluda.games.android.colors&feature=search_result
     
  10. Skolleus, 28.02.2011 #10
    Skolleus

    Skolleus Threadstarter Neuer Benutzer

    Beiträge:
    16
    Erhaltene Danke:
    0
    Registriert seit:
    21.02.2011
    Irgendwie funktioniert das nicht. Das Lunarlander Beispiel hilft mir nicht weil schon der grundsätzliche Aufbau des Programms ein völlig anderer ist und ich auch nicht zu großen Einfluss darauf nehmen kann.

    z.B. gibt es hier kein XML File, es gibt nur einen UI-Thread und einen NetworkThread....
    Hier mal eine grobe Strukturübersicht:

    Code:
    public class Main extends Activity{
             //Variablendelerationen...
             final runnable mUpdateresults = new Runnable(){
                   public void run(){
                         //Parseraufruf speichert in Datenobjekt
                         actionOnData();  
                   }
             }
             public void onCreate (Bundle savedInstanceState){
                  NetworkReader reader = new NetworkReader(this, IP, PORT);
                  super.onCreate(savedInstanceState);
                  setContentView(mGraphicsView);
                  reader.reading();
             }
    
            public void actionOnData(){}//Berechnungen mit dem Datenobjekt
    }
    
    Code:
    public class NetworkReader{
          public void reading(){
                public void run(){
                      //in=bufferedreader...
                      while ((str = in.readLine()) !=null){
                            ...
                            Main.runOnUiThread(Main.mUpdateresults);
                      }
                      ...
                }
                ...
          }
    }
    
    Code:
    public class GraphicsView extends View(){
          //Variablendeklarationen
          public GraphicsView(Context context){
                super(context);
          }
          
          protected onDraw(Canvas canvas){
                 //Variablendeklarationen
                 //berechnungen mit Datenobjekt
                 canvas.drawBitmap();//Hintergrundbild
                 canvas.drawPath();//Standardformen
          }
    }
    
    Dazu noch eine Klasse Parser und ein Datenobjekt zur Speicherung und weiterverarbeitung.

    Wenn ich jetzt statt on View von SurfaceView erben lasse, muss schon der Aufruf in der Main ein ganz anderer sein, es kommt ein weiterer Thread dazu, es muss das SurfaceHolder Interface implementiert werden.. inklusive 3 neuer Methoden. Ich müsste das halbe Programm umschreiben.
     
    Zuletzt bearbeitet: 28.02.2011
  11. ko5tik, 28.02.2011 #11
    ko5tik

    ko5tik Android-Experte

    Beiträge:
    620
    Erhaltene Danke:
    40
    Registriert seit:
    14.10.2009
    Phone:
    HTC Hero
    Vergiss runOnUiThread - es ist nicht realtime. Stattdessen machst du folgendes:
    - definierst einen Surface-View wo du zeichnen willst
    - übergibst diesen an deinen NetworkReader
    - zeichnest das was du willst so wie im Beispiel
     
    Skolleus bedankt sich.
  12. Skolleus, 28.02.2011 #12
    Skolleus

    Skolleus Threadstarter Neuer Benutzer

    Beiträge:
    16
    Erhaltene Danke:
    0
    Registriert seit:
    21.02.2011
    Jetzt sieht es so aus:

    Code:
    public class Main extends Activity{
            //Variablendelerationen...
            public void onCreate (Bundle savedInstanceState){             
                 super.onCreate(savedInstanceState);
                 mGraphicsView = new GraphicsView(this);
                 setContentView(mGraphicsView);
                 NetworkReader reader = new NetworkReader(IP, PORT, mGraphicsView);
                 reader.reading();
            }
    
    
    }
    Code:
    public class GraphicsView extends SurfaceView implements SurfaceHolder.Callback{
            //Variablendeklarationen
            public GraphicsView(Context context){
                super(context);
            }
          
            protected onDraw(Canvas canvas){
                //Variablendeklarationen
                Canvas c=null;
                try {
                     c=mHolder.lockCanvas(null);
                     c.drawBitmap();//Hintergrundbild
                     c.drawPath();//Standardformen
                }finally 
                    if(c!=null) mHolder.unlockCanvasAndPost(c);
           }
    
           public void surfaceCreated(SurfaceHolder holder){
                mHolder=holder;
           }    
    
           public void surfaceChanged(SurfaceHolder holder, int format, int width);
           public void surfaceDestroyed(SurfaceHolder holder);
    }
    
    Code:
    public class NetworkReader{
           public void reading(){
                public void run(){
                      //in=bufferedreader...
                      while ((str = in.readLine()) !=null){
                            if (mParser.parse(str)){
                                 actionOnData();
                                 mGraphicsView.doDraw();
                            }
                      }
                      ...
                }
                ...
           }
    
           public void actionOnData() //Berechnungen mit Daten aus dem Datenobjekt        
    }
    
    Kein Bild. Stattdessen eine NullpointerException beim Versuch das Bitmap zu zeichnen und eine Meldung:
    Layout repeat skipped after too many iterations
     
    Zuletzt bearbeitet: 28.02.2011
  13. ko5tik, 01.03.2011 #13
    ko5tik

    ko5tik Android-Experte

    Beiträge:
    620
    Erhaltene Danke:
    40
    Registriert seit:
    14.10.2009
    Phone:
    HTC Hero
    1. Check deinen Layout ( im XML ? )
    2. Ich würde nicht von dem SurfaceView ableiten, sondern diese einfach
    benutzen ( dein Netzwerk-Code hat dann weniger Abhängigkeiten zum android Zeugs )
    3. Ich würde wirklich empfehlen den LunarLander noch mal durchzugehen.
     
  14. Skolleus, 01.03.2011 #14
    Skolleus

    Skolleus Threadstarter Neuer Benutzer

    Beiträge:
    16
    Erhaltene Danke:
    0
    Registriert seit:
    21.02.2011
    Wie gesagt.. es gibt kein XML File.
    Wo meinst du?
    Das Beispiel hat mit meinem Problem einfach nichts zu tun.
    LunarLander hat einen Thread (neben dem Mainthread) in dem alles gemacht wird. Eingaben, Ausgaben, Berechnungen. Ich habe zusätzlich einen Netzwerkthread für die Eingabe. Und genau DA ist das Problem weil ich die Eingabe nicht mit der Ausgabe in Einklang bringen kann.
     
  15. ko5tik, 01.03.2011 #15
    ko5tik

    ko5tik Android-Experte

    Beiträge:
    620
    Erhaltene Danke:
    40
    Registriert seit:
    14.10.2009
    Phone:
    HTC Hero
    Poste mal den Stacktrace. LunarLAnder demonstriert, wie man einen SurfaceView in Echtzeit von einen separaten (nich UI) Thread updated - genau das willst du auch haben

    SurfaceView muss du von deiner Netzwerk-Thread aus benutzen
     
  16. Skolleus, 01.03.2011 #16
    Skolleus

    Skolleus Threadstarter Neuer Benutzer

    Beiträge:
    16
    Erhaltene Danke:
    0
    Registriert seit:
    21.02.2011
    Ich glaub ich habs jetzt. Musste es doch komplett umschreiben.
    Nur noch ein paar kleine Probleme..
    Was ich zeichne wird beim nächsten Durchlauf nicht gelöscht. Gibts da eine "schöne" Methode um gezeichnete Linien beim nächsten Durchlauf nicht mehr zu zeigen?
    Außerdem funktioniert plötzlich der Orientierungswechsel (Landscape, Portrait) nicht mehr. Daran hab ich gar nichts geändert.
     
    Zuletzt bearbeitet: 01.03.2011
  17. Skolleus, 02.03.2011 #17
    Skolleus

    Skolleus Threadstarter Neuer Benutzer

    Beiträge:
    16
    Erhaltene Danke:
    0
    Registriert seit:
    21.02.2011
    OK das Problem mit dem Orientation-wechsel ist auch vom Tisch.. hatte vergessen das Manifest wieder anzupassen.

    Danke für deine Hilfe und Geduld mit mir :)

    Nur noch die Sache mit dem neu Zeichnen bei jedem Durchgang. Ich hab schon versucht gleich am Anfang der onDraw() Methode ein
    Code:
    canvas.drawColor(Color.Black);
    oder ein
    Code:
    paint.setColor(Color.Black);
    canvas drawRect(new Rect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);
    
    zu setzen. Leider hat das nichts gebracht. Ich fänds auch sehr unschön..
    Die gezeichneten Linien werden beim nächsten Durchlauf nicht entfernt.

    edit: Das ist ja ein neues Problem.. also neuer ForenThread..
     
    Zuletzt bearbeitet: 02.03.2011

Diese Seite empfehlen