Timer um Methoden in x Sekunden zu wiederholen

  • 7 Antworten
  • Letztes Antwortdatum
W

WolfCH

Ambitioniertes Mitglied
1
Hallo Zusammen

Ich bin bisher nun gut vorangekommen.
Habe einen AsyncTask im Hintergrund welcher aus dem Internet ein Bild lädt und in einer ImageView anzeigt. Nun möchte ich dass dieses alle 30 Sekunden automatisch neu geladen und angezeigt wird, gleichzeitig soll ein TextView z.B. bei 30 Sek beginnen und immer 1 Weniger zählen und bei 0 wenn die 30sek durch sind das Bild neu geladen werden und auch TextView wieder auf 30 setzen.

Ich versuchte das ganze nun mit einem Timer, es will aber noch nicht so recht:
PHP:
else if(number == 3)
            {

                //Rufe neues Layout auf
                final View boView = inflater.inflate(R.layout.fragment_bo, container, false);
                //TextView initalisiern und Text setzen
                TextView textViewBo = (TextView) boView.findViewById(R.id.bo_text);
                final TextView textViewInterval = (TextView) boView.findViewById(R.id.interval);
                textViewBo.setText("Aktuelle Blitzdaten");
                //ImageView initalisieren und Bild aus Blitzortung laden (BETA)
                final ImageView myIv = (ImageView) boView.findViewById(R.id.imageBo);
                final String URL = "http://blitzortung.ssv-ufs.ch/blitzortung.php?map=4";


                Timer t = new Timer();
                Timer txt = new Timer();

                t.schedule(new TimerTask(){

                    @Override
                    public void run() {
                        myIv.setTag(URL);
                        new DownloadImagesTask().execute(myIv);
                    }

                }, 0, 30000); //alle 30 sekunden...

                txt.schedule(new TimerTask() {
                public int interval = 30;
                @Override
                public void run() {
                interval = interval-1;
                //TEXT INTERVAL
                textViewInterval.setText(Integer.toString(interval));
            }

            }, 0, 1000); //alle 1 sekunden...

                //Variable Return
                return boView;
            }

Der AsyncTask sieht folgendermassen aus:
PHP:
  public class DownloadImagesTask extends AsyncTask<ImageView, Void, Bitmap> {

            ImageView imageView = null;

            @Override
            protected Bitmap doInBackground(ImageView... imageViews) {
                this.imageView = imageViews[0];
                return download_Image((String) imageView.getTag());
            }

            @Override
            protected void onPostExecute(Bitmap result) {
                imageView.setImageBitmap(result);
            }


            private Bitmap download_Image(String url) {
                Bitmap bm = null;
                try {
                    URL aURL = new URL(url);
                    URLConnection conn = aURL.openConnection();
                    conn.connect();
                    InputStream is = conn.getInputStream();
                    BufferedInputStream bis = new BufferedInputStream(is);
                    bm = BitmapFactory.decodeStream(bis);
                    bis.close();
                    is.close();
                } catch (IOException e) {
                    Log.e("Hub", "Error getting the image from server : " + e.getMessage().toString());
                }
                return bm;
            }
        }

Beim Setzen des TextViews stürzt die App ab.
Logcat sagt:
04-15 14:59:24.636 1408-1431/com.stormchase.ssv_ufsig.ch E/AndroidRuntime FATAL EXCEPTION: Timer-1
Process: com.stormchase.ssv_ufsig.ch, PID: 1408
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6094)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:824)
at android.view.View.requestLayout(View.java:16431)
at android.view.View.requestLayout(View.java:16431)
at android.view.View.requestLayout(View.java:16431)
at android.view.View.requestLayout(View.java:16431)
at android.support.v4.widget.DrawerLayout.requestLayout(DrawerLayout.java:762)
at android.view.View.requestLayout(View.java:16431)
at android.view.View.requestLayout(View.java:16431)
at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:352)
at android.view.View.requestLayout(View.java:16431)
at android.widget.TextView.checkForRelayout(TextView.java:6600)
at android.widget.TextView.setText(TextView.java:3813)
at android.widget.TextView.setText(TextView.java:3671)
at android.widget.TextView.setText(TextView.java:3646)
at com.stormchase.ssv_ufsig.ch.MainActivity$PlaceholderFragment$2.run(MainActivity.java:263)
at java.util.Timer$TimerImpl.run(Timer.java:284)
04-15 14:59:26.076 1408-1432/com.stormchase.ssv_ufsig.ch D/dalv

Geht das so überhaupt? Fehlermeldung selbst bei der Compilierung erhalte ich keine.

Bin froh für Ansätze.
Anmerkdung: Das ganze läuft in einer Activity mit Fragmente, im Fragment wurde ein neues Layout gealden (fragment_bo) in dessem sich die TextViews und ImageViews befinden. else if(number == 3) leitet vom Slidemenu ab, wenn Position 3 gewählt wurde. :)

Lasse ich die Timer weg und das Bild anzeigen, d.h. AsyncTask nur einmal ausführen dann klappts, aber alle 30sek geht nich.

EDIT:
Ich hab jetzt das mit dem setText und TextView (Timer für 1sek) auskommentiert und den 30sek belassen.
Kein Absturz mehr, das Bild wird korrekt alle paar Sekunden aktualisiert, nur das mit Textview klappt noch nicht.

Danke schon jetzt für die Anregungen.

Grüsse aus der Schweiz
 
Zuletzt bearbeitet:
Code:
... Only the original thread that created a view hierarchy can touch its views 
... at android.widget.TextView.setText(TextView.java:3646  )
at com.stormchase.ssv_ufsig.ch.MainActivity$Placehold  erFragment$2.run(MainActivity.java:263)
at java.util.Timer$TimerImpl.run(Timer.java:284)
Sprich du hast versucht aus einem Thread ein View zu verändern (setText), der in einem anderen Thread erzeugt wurde. Der Fehler wurde von einem deiner Timer verursacht.

In Zeile 263 steht wahrscheinlich der Code, der den Fehler verursacht.:
Code:
[COLOR=#0000BB]textViewInterval[/COLOR][COLOR=#007700].[/COLOR][COLOR=#0000BB]setText[/COLOR][COLOR=#007700]([/COLOR][COLOR=#0000BB]Integer[/COLOR][COLOR=#007700].[/COLOR][COLOR=#0000BB]toString[/COLOR][COLOR=#007700]([/COLOR][COLOR=#0000BB]interval[/COLOR][COLOR=#007700]));[/COLOR]
(Denke, das ist Zeile 263).

Aus der Fehlermeldung geht noch hervor, das der View zum PlaceholdFragment gehört. Eine Möglichkeit für dein Problem wäre ein Handler.
Android App Development:Threading part 1: Handlers | Mobile Orchard
Handler | Android Developers

Damit ist es bei dir aber nicht getan. Wenn ich richtig gezählt habe, hast du 5 Threads am Start. (2 Timer, ein AsyncTask(Unter der Haube stekt auch ein Thread), ein Thread für dein Fragment und der UI-Thread).
Und die greifen wild in einander.

Keine gute wirklich Idee. Am besten verringerst du die Abhängigkeiten zwischen den Threads und die Anzahl der Threads. Wie genau, kann ich Dir leider nicht sagen, da ich nur ein Bruchteil deines Codes kenne.
 
  • Danke
Reaktionen: WolfCH
Hm.. demnach wäre wohl besser das ganze in eine eigene Activity auszulagern und diese dann einfach einzubinden in das Fragment? Ich habe die App mit der Vorlage erstellt "NavigationDrawer" und da läufts mit Fragmenten ab. Das einzige was ich selbst nun bisher gemacht habe sind die Menueinträge hinzugefügt, ein neues XML Layout welches ich mit Klick auf Button Nummer 3 (else if(number ==3)) lade und dann dort den Code ausführe wie AsyncTask, doInBackground sowie das mit den Timern.

Um Threads unabhängig zu machen müsste ich demnach den vorgegebenen Code bezüglich Fragments modifizieren? Oder wäre es ggf. Sinnvoller die Timer direkt in AsyncTask zu verschieben ?
 
Hallo,

wenn du das mit AsyncTask (AT) lösen möchtest, wäre ein Stichwort die Methode "onProgessUpdate".

Meiner Ansicht nach ist der AT jedoch nicht notwendig.
Du kannst innerhalb deines Timers einen Handler verwenden, der dir den Zugriff auf deine Views gestattet.

Code:
t = new Timer();
h = new Handler();

myTimer.schedule(new TimerTask() {  
			
			@Override    
			public void run() {
				
				// Hier der seperate Thread im Hintergrund
				
				//** Handler to Update the GUI *//*
				myHandler.post(new Runnable() {            
					@Override            
					public void run() {                
							
						// Hier das GUI Updaten
					}       
				});    
			};
		}, 0L, 1L * 1000);
 
  • Danke
Reaktionen: WolfCH
Hi Flocke

Danke für den Ansatz, aber wenn ich ein Bild aus dem Internet laden möchte ist der AsyncTask mit doInBackground schon besser, ich weiss ja nicht welche Internetleistung der jeweilige Nutzer unterwegs dann hat, während des Downloads sollte dies ja nicht blockiert werden.

Daher hab ich die Überlegung den Timer direkt im AsyncTask zu implementieren. Ich werde jetzt ein paar Varianten durchspielen.

Dankeschöööön
 
Ich würde aus den Fragment PlaceholderFragment eine normale Klasse machen.
Da der View im PlaceholderFragment verändert wird, gehört der Timer in das Fragment und nicht in die Activity. So brauchst du kein Handler.

Ich würde auch nur ein Timer benutzen mit einer Periodedauer von 1000ms. Immer wenn der Counter auf null ist, lädst die App das ImageView herunter. Sprich, beide Ereignisse werden von nur ein Timer Thread ausgelöst.
Den Aufruf des AsyncTask packst du in eine Methode des Fragments.
Code:
[COLOR=#000000][COLOR=#0000BB]
private void startTimer(){
   myIv[/COLOR][COLOR=#007700].[/COLOR][COLOR=#0000BB]setTag[/COLOR][COLOR=#007700]([/COLOR][COLOR=#0000BB]URL[/COLOR][COLOR=#007700]);
   new [/COLOR][COLOR=#0000BB]DownloadImagesTask[/COLOR][COLOR=#007700]().[/COLOR][COLOR=#0000BB]execute[/COLOR][COLOR=#007700]([/COLOR][COLOR=#0000BB]myIv[/COLOR][COLOR=#007700]);[/COLOR][/COLOR][COLOR=#000000][COLOR=#0000BB]
}[/COLOR][/COLOR]
Der Trick dabei ist, die Methode befindet sich im Thread des Fragment, und nicht im Timer Thread.
Das gleiche machst du am besten auch mit den View.

Statt eine Aufgabe auf mehrere Klassen zu verteilen, gehe umgekehrt vor.
Der Countdown gehört in den Timer, der Download in den AsyncTask, und beide in das Fragment. So hat jeder der Klassen nur eine Aufgabe (OOP). Und die Activity bekommt nicht davon mit. ;)
 
  • Danke
Reaktionen: WolfCH
Hm.. und ich kann ganz einfach das PlaceholderFragment in eine Klasse umschreiben ohne irgendwelche "Komplikationen" mit dem NavigationDrawer etc?

Ich habe es jetzt etwas unelegant so gelöst:
PHP:
//Rufe neues Layout auf
                final View boView = inflater.inflate(R.layout.fragment_bo, container, false);
                //TextView initalisiern und Text setzen
                TextView textViewBo = (TextView) boView.findViewById(R.id.bo_text);
                textViewBo.setText("Aktuelle Blitzdaten");
                //ImageView initalisieren und Bild aus Blitzortung laden (BETA)
                final ImageView myIv = (ImageView) boView.findViewById(R.id.imageBo);
                final String URL = "http://blitzortung.ssv-ufs.ch/blitzortung.php?map=4";

                final Handler h = new Handler();
                Timer txt = new Timer();
                txt.schedule(new TimerTask() {
                    public int interval = 30;
                    @Override
                    public void run() {
                        // Hier der seperate Thread im Hintergrund
                        //** Handler to Update the GUI *//*
                        h.post(new Runnable() {
                            @Override
                            public void run() {
                                // Hier das GUI Updaten
                                final TextView textViewInterval = (TextView) boView.findViewById(R.id.interval);
                                textViewInterval.setText(Integer.toString(interval));
                                if(interval == 0) {
                                textViewInterval.setText("Aktualisieren...");
                                interval = 29;
                                }
                                else
                                {
                                    textViewInterval.setText(Integer.toString(interval));
                                    interval--;
                                }
                            }
                        });
                    }

                    ;
                }, 0L, 1L * 1000);

                Timer img = new Timer();

                img.schedule(new TimerTask() {

                    @Override
                    public void run() {
                        myIv.setTag(URL);
                        new DownloadImagesTask().execute(myIv);
                    }

                }, 0, 30000); //alle 30 sekunden...
 
Natürlich kannst man das. Bei mir ist kein Fragment eine innere Klasse. Für was ist der Befehl import den sonst zu gebrauchen.

https://www.dpunkt.de/java/Die_Sprache_Java/Objektorientierte_Programmierung_mit_Java/31.html
Galileo Computing :: Java ist auch eine Insel - 3 Klassen und Objekte

Wie das mit Fragmente geht: Fragments | Android Developers

Aber du solltest dir angewöhnen eine Aufgabe eine Methode oder Klasse. Sobald du Funktionalität über große Bereiche verteilst, wird es bei größeren Projekten echt kompliziert. Und der Code ist nicht mehr wartbar. Jede Änderung wird zu einer Herkulesarbeit.
 
Zuletzt bearbeitet:
Zurück
Oben Unten