Eine Aufgabe zeitverzögert wiederholen?


216

Ich habe eine Variable in meinem Code, die sagt, es sei "Status".

Ich möchte in Abhängigkeit von diesem Variablenwert Text in der Anwendung anzeigen. Dies muss mit einer bestimmten Zeitverzögerung erfolgen.

Es ist wie,

  • Überprüfen Sie den Wert der Statusvariablen

  • Zeigen Sie Text an

  • Warten Sie 10 Sekunden

  • Überprüfen Sie den Wert der Statusvariablen

  • Zeigen Sie Text an

  • Warten Sie 15 Sekunden

und so weiter. Die Zeitverzögerung kann variieren und wird eingestellt, sobald der Text angezeigt wird.

Ich habe es versucht Thread.sleep(time delay)und es ist fehlgeschlagen. Gibt es einen besseren Weg, um dies zu erreichen?


Antworten:


448

Sie sollten verwenden Handler‚s - postDelayedFunktion für diesen Zweck. Ihr Code wird mit der angegebenen Verzögerung im Haupt-UI-Thread ausgeführt, sodass Sie die UI-Steuerelemente aktualisieren können.

private int mInterval = 5000; // 5 seconds by default, can be changed later
private Handler mHandler;

@Override
protected void onCreate(Bundle bundle) {

    // your code here

    mHandler = new Handler();
    startRepeatingTask();
}

@Override
public void onDestroy() {
    super.onDestroy();
    stopRepeatingTask();
}

Runnable mStatusChecker = new Runnable() {
    @Override 
    public void run() {
          try {
               updateStatus(); //this function can change value of mInterval.
          } finally {
               // 100% guarantee that this always happens, even if
               // your update method throws an exception
               mHandler.postDelayed(mStatusChecker, mInterval);
          }
    }
};

void startRepeatingTask() {
    mStatusChecker.run(); 
}

void stopRepeatingTask() {
    mHandler.removeCallbacks(mStatusChecker);
}

1
Danke inazaruk, habe es geschafft, es zum Laufen zu bringen. Es wurden 2 v kleine Tippfehler gefunden (oben "Handler", nicht "Handle" und unten "removeCallbacks", nicht "removecallback". Aber auf jeden Fall war der Code genau das, wonach ich gesucht habe. Ich habe versucht zu überlegen, was ich kann tun, um den Gefallen zu erwidern. Zumindest haben Sie meinen Respekt gewonnen. Mit freundlichen Grüßen Aubrey Bourke.
Aubreybourke

20
Schönes Programm, funktioniert absolut gut. Aber die startRepeatingTask () musste vom onCreate-Methoden- / UI-Thread aufgerufen werden (ich habe einige Zeit gebraucht, um dies zu realisieren!), Vielleicht hätte dieser Punkt irgendwo erwähnt werden können. Grüße
gkris

1
Ihre Antwort gibt weiter. Das hat mir heute aus einem Loch geholfen. Vielen Dank.
Dean Blakely

Gibt es eine Möglichkeit, ein wiederholtes Runnable in der getView () -Methode eines Adapters zu haben?
toobsco42

1
Was sollen wir hier importieren, wenn wir Klassen importieren? android.os.Handler oder java.util.logging.Handler?
EJ Chathuranga

34

Für alle Interessierten ist hier eine Klasse, die ich mit inazaruks Code erstellt habe und die alles Notwendige erstellt (ich habe sie UIUpdater genannt, weil ich damit die Benutzeroberfläche regelmäßig aktualisiere, aber Sie können sie beliebig nennen):

import android.os.Handler;
/**
 * A class used to perform periodical updates,
 * specified inside a runnable object. An update interval
 * may be specified (otherwise, the class will perform the 
 * update every 2 seconds).
 * 
 * @author Carlos Simões
 */
public class UIUpdater {
        // Create a Handler that uses the Main Looper to run in
        private Handler mHandler = new Handler(Looper.getMainLooper());

        private Runnable mStatusChecker;
        private int UPDATE_INTERVAL = 2000;

        /**
         * Creates an UIUpdater object, that can be used to
         * perform UIUpdates on a specified time interval.
         * 
         * @param uiUpdater A runnable containing the update routine.
         */
        public UIUpdater(final Runnable uiUpdater) {
            mStatusChecker = new Runnable() {
                @Override
                public void run() {
                    // Run the passed runnable
                    uiUpdater.run();
                    // Re-run it after the update interval
                    mHandler.postDelayed(this, UPDATE_INTERVAL);
                }
            };
        }

        /**
         * The same as the default constructor, but specifying the
         * intended update interval.
         * 
         * @param uiUpdater A runnable containing the update routine.
         * @param interval  The interval over which the routine
         *                  should run (milliseconds).
         */
        public UIUpdater(Runnable uiUpdater, int interval){
            UPDATE_INTERVAL = interval;
            this(uiUpdater);
        }

        /**
         * Starts the periodical update routine (mStatusChecker 
         * adds the callback to the handler).
         */
        public synchronized void startUpdates(){
            mStatusChecker.run();
        }

        /**
         * Stops the periodical update routine from running,
         * by removing the callback.
         */
        public synchronized void stopUpdates(){
            mHandler.removeCallbacks(mStatusChecker);
        }
}

Sie können dann ein UIUpdater-Objekt in Ihrer Klasse erstellen und wie folgt verwenden:

...
mUIUpdater = new UIUpdater(new Runnable() {
         @Override 
         public void run() {
            // do stuff ...
         }
    });

// Start updates
mUIUpdater.startUpdates();

// Stop updates
mUIUpdater.stopUpdates();
...

Wenn Sie dies als Aktivitätsaktualisierungsprogramm verwenden möchten, fügen Sie den Startaufruf in die onResume () -Methode und den Stoppaufruf in die onPause () -Methode ein, damit die Aktualisierungen entsprechend der Aktivitätssichtbarkeit gestartet und gestoppt werden.


1
Bearbeitet: UPDATE_INTERVAL = interval;sollte vor this(uiUpdater); in stehen UIUpdater(Runnable uiUpdater, int interval)(da der Wert von UPDATE_INTERVALverwendet wird und als Parameter übergeben werden sollte interval;). Vermeiden Sie auch eine Breite von über 80 Zeichen im Code, wenn möglich (fast immer;)
Mr_and_Mrs_D

5
Diese Klasse hat eine Reihe von Problemen. Zunächst sollte es im Hauptthread instanziiert werden, um die GUI aktualisieren zu können. Sie hätten dies lösen können, indem Sie den Hauptschleifer an den Handlerkonstruktor übergeben : new Handler(Looper.getMainLooper()). Zweitens werden keine Argumente validiert, sodass keine Runnables und negativen Intervalle verschluckt werden. Schließlich werden weder die in der uiUpdater.run()Zeile verbrachte Zeit berücksichtigt noch mögliche Ausnahmen behandelt, die von dieser Methode ausgelöst werden. Auch ist es nicht threadsicher, Sie sollten Methoden erstellen startund stopsynchronisieren.
Mister Smith

2
Bis zum Argumentvalidierungsteil bearbeitet, da ich hier keine Eclipse habe, um den Code zu testen. Danke für die Rückmeldung! Hast du das gemeint? Synchronisierte startUpdates und stopUpdates und füge einen Looper.getMainLooper () -Aufruf in den Handler-Konstruktor ein (ich hoffe, du kannst ihn direkt aus der
Felddeklaration aufrufen

2
Ich verstehe das: error: call to this must be first statement in constructorVielleicht gibt es eine einfache Lösung.
Msysmilu

4
Upvoting für den Import - braucht Zeit, um herauszufinden, woher Handler kommt, wenn er beiläufig in Java programmiert
Roman Susi

23

Ich denke, die neue Hotness ist die Verwendung eines ScheduledThreadPoolExecutor . Wie so:

private final ScheduledThreadPoolExecutor executor_ = 
        new ScheduledThreadPoolExecutor(1);
this.executor_.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
    update();
    }
}, 0L, kPeriod, kTimeUnit);

Executors.newSingleThreadScheduledExecutor()kann hier eine andere Option sein.
Gulshan

13

Timer funktioniert gut. Hier verwende ich Timer, um Text nach 1,5 Sekunden zu suchen und die Benutzeroberfläche zu aktualisieren. Hoffentlich hilft das.

private Timer _timer = new Timer();

_timer.schedule(new TimerTask() {
    @Override
    public void run() {
        // use runOnUiThread(Runnable action)
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                search();
            }
        });
    }
}, timeInterval);

Wo hast du die Intervallzeit hingelegt?
Nathiel Barros

1
Hallo Nathiel, ich habe gerade meinen Beitrag aktualisiert, hoffe es hilft! Die Intervallzeit ist der zweite Parameter von Timer.schedule ().
Kai Wang

7

Es gibt drei Möglichkeiten, dies zu tun:

Verwenden Sie ScheduledThreadPoolExecutor

Ein bisschen übertrieben, da du keinen Thread-Pool brauchst

   //----------------------SCHEDULER-------------------------
    private final ScheduledThreadPoolExecutor executor_ =
            new ScheduledThreadPoolExecutor(1);
     ScheduledFuture<?> schedulerFuture;
   public void  startScheduler() {
       schedulerFuture=  executor_.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                //DO YOUR THINGS
                pageIndexSwitcher.setVisibility(View.GONE);
            }
        }, 0L, 5*MILLI_SEC,  TimeUnit.MILLISECONDS);
    }


    public void  stopScheduler() {
        pageIndexSwitcher.setVisibility(View.VISIBLE);
        schedulerFuture.cancel(false);
        startScheduler();
    }

Timer-Aufgabe verwenden

Alter Android-Stil

    //----------------------TIMER  TASK-------------------------

    private Timer carousalTimer;
    private void startTimer() {
        carousalTimer = new Timer(); // At this line a new Thread will be created
        carousalTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                //DO YOUR THINGS
                pageIndexSwitcher.setVisibility(INVISIBLE);
            }
        }, 0, 5 * MILLI_SEC); // delay
    }

    void stopTimer() {
        carousalTimer.cancel();
    }

Verwenden Sie Handler und Runnable

Moderner Android-Stil

    //----------------------HANDLER-------------------------

    private Handler taskHandler = new android.os.Handler();

    private Runnable repeatativeTaskRunnable = new Runnable() {
        public void run() {
            //DO YOUR THINGS
        }
    };

   void startHandler() {
        taskHandler.postDelayed(repeatativeTaskRunnable, 5 * MILLI_SEC);
    }

    void stopHandler() {
        taskHandler.removeCallbacks(repeatativeTaskRunnable);
    }

Nicht undichter Handler mit Aktivität / Kontext

Deklarieren Sie eine innere Handler-Klasse, bei der in Ihrer Activity / Fragment-Klasse kein Speicher verloren geht

/**
     * Instances of static inner classes do not hold an implicit
     * reference to their outer class.
     */
    private static class NonLeakyHandler extends Handler {
        private final WeakReference<FlashActivity> mActivity;

        public NonLeakyHandler(FlashActivity activity) {
            mActivity = new WeakReference<FlashActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            FlashActivity activity = mActivity.get();
            if (activity != null) {
                // ...
            }
        }
    }

Deklarieren Sie eine ausführbare Datei, die Ihre sich wiederholende Aufgabe in Ihrer Aktivitäts- / Fragmentklasse ausführt

   private Runnable repeatativeTaskRunnable = new Runnable() {
        public void run() {
            new Handler(getMainLooper()).post(new Runnable() {
                @Override
                public void run() {

         //DO YOUR THINGS
        }
    };

Initialisieren Sie das Handler-Objekt in Ihrer Aktivität / Ihrem Fragment (hier ist FlashActivity meine Aktivitätsklasse).

//Task Handler
private Handler taskHandler = new NonLeakyHandler(FlashActivity.this);

Wiederholen einer Aufgabe nach einem festgelegten Zeitintervall

taskHandler.postDelayed (repeatativeTaskRunnable, DELAY_MILLIS);

Um die Wiederholung der Aufgabe zu stoppen

taskHandler .removeCallbacks (repeatativeTaskRunnable);

UPDATE: In Kotlin:

    //update interval for widget
    override val UPDATE_INTERVAL = 1000L

    //Handler to repeat update
    private val updateWidgetHandler = Handler()

    //runnable to update widget
    private var updateWidgetRunnable: Runnable = Runnable {
        run {
            //Update UI
            updateWidget()
            // Re-run it after the update interval
            updateWidgetHandler.postDelayed(updateWidgetRunnable, UPDATE_INTERVAL)
        }

    }

 // SATART updating in foreground
 override fun onResume() {
        super.onResume()
        updateWidgetHandler.postDelayed(updateWidgetRunnable, UPDATE_INTERVAL)
    }


    // REMOVE callback if app in background
    override fun onPause() {
        super.onPause()
        updateWidgetHandler.removeCallbacks(updateWidgetRunnable);
    }

6

Der Timer ist eine weitere Möglichkeit, Ihre Arbeit zu erledigen. Fügen Sie ihn jedoch unbedingt hinzu, runOnUiThreadwenn Sie mit der Benutzeroberfläche arbeiten.

    import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;
import android.app.Activity;

public class MainActivity extends Activity {

 CheckBox optSingleShot;
 Button btnStart, btnCancel;
 TextView textCounter;

 Timer timer;
 MyTimerTask myTimerTask;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  optSingleShot = (CheckBox)findViewById(R.id.singleshot);
  btnStart = (Button)findViewById(R.id.start);
  btnCancel = (Button)findViewById(R.id.cancel);
  textCounter = (TextView)findViewById(R.id.counter);

  btnStart.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {

    if(timer != null){
     timer.cancel();
    }

    //re-schedule timer here
    //otherwise, IllegalStateException of
    //"TimerTask is scheduled already" 
    //will be thrown
    timer = new Timer();
    myTimerTask = new MyTimerTask();

    if(optSingleShot.isChecked()){
     //singleshot delay 1000 ms
     timer.schedule(myTimerTask, 1000);
    }else{
     //delay 1000ms, repeat in 5000ms
     timer.schedule(myTimerTask, 1000, 5000);
    }
   }});

  btnCancel.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    if (timer!=null){
     timer.cancel();
     timer = null;
    }
   }
  });

 }

 class MyTimerTask extends TimerTask {

  @Override
  public void run() {
   Calendar calendar = Calendar.getInstance();
   SimpleDateFormat simpleDateFormat = 
     new SimpleDateFormat("dd:MMMM:yyyy HH:mm:ss a");
   final String strDate = simpleDateFormat.format(calendar.getTime());

   runOnUiThread(new Runnable(){

    @Override
    public void run() {
     textCounter.setText(strDate);
    }});
  }

 }

}

und xml ist ...

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:autoLink="web"
    android:text="http://android-er.blogspot.com/"
    android:textStyle="bold" />
<CheckBox 
    android:id="@+id/singleshot"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Single Shot"/>

Eine andere Möglichkeit, CountDownTimer zu verwenden

new CountDownTimer(30000, 1000) {

     public void onTick(long millisUntilFinished) {
         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
     }

     public void onFinish() {
         mTextField.setText("done!");
     }
  }.start();

Planen Sie einen Countdown bis zu einem späteren Zeitpunkt mit regelmäßigen Benachrichtigungen in Intervallen auf dem Weg. Beispiel für die Anzeige eines 30-Sekunden-Countdowns in einem Textfeld:

Für Details


1
Der Handler wird dem Timer vorgezogen. Siehe Timer vs Handler
Suragch

4

Versuchen Sie folgendes Beispiel, es funktioniert !!!

Verwenden Sie [Handler] in der onCreate () -Methode, die die postDelayed () -Methode verwendet, mit der die ausführbare Datei zur Nachrichtenwarteschlange hinzugefügt wird, nachdem die angegebene Zeitspanne abgelaufen ist, die im angegebenen Beispiel 0 ist. 1

Verweisen Sie auf diesen Code:

public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
    //------------------
    //------------------
    android.os.Handler customHandler = new android.os.Handler();
            customHandler.postDelayed(updateTimerThread, 0);
}

private Runnable updateTimerThread = new Runnable()
{
        public void run()
        {
            //write here whaterver you want to repeat
            customHandler.postDelayed(this, 1000);
        }
};



4

Basierend auf dem obigen Beitrag zum ScheduledThreadPoolExecutor habe ich ein Dienstprogramm entwickelt, das meinen Anforderungen entspricht (ich wollte alle 3 Sekunden eine Methode auslösen):

class MyActivity {
    private ScheduledThreadPoolExecutor mDialogDaemon;

    private void initDebugButtons() {
        Button btnSpawnDialogs = (Button)findViewById(R.id.btn_spawn_dialogs);
        btnSpawnDialogs.setVisibility(View.VISIBLE);
        btnSpawnDialogs.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                spawnDialogs();
            }
        });
    }

    private void spawnDialogs() {
        if (mDialogDaemon != null) {
            mDialogDaemon.shutdown();
            mDialogDaemon = null;
        }
        mDialogDaemon = new ScheduledThreadPoolExecutor(1);
        // This process will execute immediately, then execute every 3 seconds.
        mDialogDaemon.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // Do something worthwhile
                    }
                });
            }
        }, 0L, 3000L, TimeUnit.MILLISECONDS);
    }
}

4

In meinem Fall musste ich einen Prozess ausführen, wenn eine dieser Bedingungen erfüllt war: wenn ein vorheriger Prozess abgeschlossen wurde oder wenn bereits 5 Sekunden vergangen waren. Also habe ich folgendes gemacht und ziemlich gut gearbeitet:

private Runnable mStatusChecker;
private Handler mHandler;

class {
method() {
  mStatusChecker = new Runnable() {
            int times = 0;
            @Override
            public void run() {
                if (times < 5) {
                    if (process1.isRead()) {
                        executeProcess2();
                    } else {
                        times++;
                        mHandler.postDelayed(mStatusChecker, 1000);
                    }
                } else {
                    executeProcess2();
                }
            }
        };

        mHandler = new Handler();
        startRepeatingTask();
}

    void startRepeatingTask() {
       mStatusChecker.run();
    }

    void stopRepeatingTask() {
        mHandler.removeCallbacks(mStatusChecker);
    }


}

Wenn Prozess1 gelesen wird, wird Prozess2 ausgeführt. Wenn nicht, werden die variablen Zeiten erhöht und der Handler nach einer Sekunde ausgeführt. Es behält eine Schleife bei, bis process1 gelesen wird oder times 5 ist. Wenn times 5 ist, bedeutet dies, dass 5 Sekunden vergangen sind und in jeder Sekunde die if-Klausel von process1.isRead () ausgeführt wird.


1

Wenn Sie Kotlin und seine Coroutine verwenden, ist es ganz einfach, zuerst einen Job in Ihrer Klasse (besser in Ihrem viewModel) wie folgt zu deklarieren:

private var repeatableJob: Job? = null

Wenn Sie es dann erstellen und starten möchten, gehen Sie folgendermaßen vor:

repeatableJob = viewModelScope.launch {
    while (isActive) {
         delay(5_000)
         loadAlbums(iImageAPI, titleHeader, true)
    }
}
repeatableJob?.start()

und wenn du es beenden willst:

repeatableJob?.cancel()

PS: viewModelScope ist nur in Ansichtsmodellen verfügbar. Sie können auch andere Coroutine-Bereiche verwenden, zwithContext(Dispatchers.IO)

Weitere Informationen: Hier


0

Für Benutzer von Kotlin funktioniert die Antwort von inazaruk nicht. Für die IDE muss die Variable initialisiert werden. Anstatt das postDelayedInnere von zu verwenden Runnable, verwenden wir sie in einer separaten Methode.

  • Initialisieren Sie dies Runnablewie folgt :

    private var myRunnable = Runnable {
        //Do some work
        //Magic happens here ↓
        runDelayedHandler(1000)   }
  • Initialisieren Sie Ihre runDelayedHandlerMethode folgendermaßen:

     private fun runDelayedHandler(timeToWait : Long) {
        if (!keepRunning) {
            //Stop your handler
            handler.removeCallbacksAndMessages(null)
            //Do something here, this acts like onHandlerStop
        }
        else {
            //Keep it running
            handler.postDelayed(myRunnable, timeToWait)
        }
    }
  • Wie Sie sehen können, können Sie mit diesem Ansatz die Lebensdauer der Aufgabe steuern. Wenn Sie keepRunningdiese während der Lebensdauer der Anwendung verfolgen und ändern, wird dies für Sie erledigt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.