Répéter une tâche avec un délai?

216

J'ai une variable dans mon code qui dit que c'est "status".

Je souhaite afficher du texte dans l'application en fonction de cette valeur de variable. Cela doit être fait avec un délai spécifique.

C'est comme,

  • Vérifier la valeur de la variable d'état

  • Afficher du texte

  • Attendez 10 secondes

  • Vérifier la valeur de la variable d'état

  • Afficher du texte

  • Attendez 15 secondes

etc. Le délai peut varier et il est défini une fois le texte affiché.

J'ai essayé Thread.sleep(time delay)et ça a échoué. Une meilleure façon de faire cela?

Kalpa Pathum Welivitigoda
la source

Réponses:

448

Vous devez utiliser Handlerla postDelayedfonction de dans ce but. Il exécutera votre code avec un délai spécifié sur le thread d'interface utilisateur principal, vous pourrez donc mettre à jour les contrôles d'interface utilisateur.

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);
}
inazaruk
la source
1
Merci inazaruk, a réussi à le faire fonctionner. Trouvé 2 v petites fautes de frappe (en haut son "Handler" pas "Handle" et en bas son "removeCallbacks" pas supprimer "removecallback". Mais en tout cas le code était exactement ce que je cherchais. Essayer de penser ce que je peux faire pour retourner la faveur. Au moins vous avez gagné mon respect. Cordialement, Aubrey Bourke.
aubreybourke
20
Bon programme, fonctionne très bien. Mais la startRepeatingTask () devait être appelée à partir de la méthode onCreate / thread d'interface utilisateur (il m'a fallu un certain temps pour le réaliser!), Peut-être que ce point aurait pu être mentionné quelque part. Cordialement
gkris
1
votre réponse continue de donner. Cela m'a aidé à sortir d'un trou aujourd'hui. Merci.
Dean Blakely
Existe-t-il un moyen d'avoir un Runnable répété dans la méthode getView () d'un adaptateur?
toobsco42
1
Ici, lorsque nous importons des classes, que devons-nous importer? android.os.Handler ou java.util.logging.Handler?
EJ Chathuranga
34

Pour toute personne intéressée, voici une classe que j'ai créée en utilisant le code d'inazaruk qui crée tout le nécessaire (je l'ai appelé UIUpdater parce que je l'utilise pour mettre à jour périodiquement l'interface utilisateur, mais vous pouvez l'appeler comme vous voulez):

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);
        }
}

Vous pouvez ensuite créer un objet UIUpdater dans votre classe et l'utiliser comme ceci:

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

// Start updates
mUIUpdater.startUpdates();

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

Si vous souhaitez l'utiliser comme programme de mise à jour d'activité, placez l'appel de démarrage dans la méthode onResume () et l'appel d'arrêt dans onPause (), afin que les mises à jour démarrent et s'arrêtent en fonction de la visibilité de l'activité.

ravemir
la source
1
Modifié: UPDATE_INTERVAL = interval;doit être avant this(uiUpdater); dans UIUpdater(Runnable uiUpdater, int interval)(car la valeur de UPDATE_INTERVALest utilisée et doit être celle transmise comme paramètre interval;). Évitez également la largeur de plus de 80 caractères dans le code lorsque cela est possible (presque toujours;)
Mr_and_Mrs_D
5
Cette classe a un certain nombre de problèmes. En premier lieu, il doit être instancié dans le thread principal pour pouvoir mettre à jour l'interface graphique. Vous auriez pu résolu ce problème en passant le principal looper au constructeur de gestionnaire: new Handler(Looper.getMainLooper()). Deuxièmement, il ne valide pas les arguments, il avale donc les Runnables nuls et les intervalles négatifs. Enfin, il ne prend pas en compte le temps passé sur la uiUpdater.run()ligne, ni ne gère les éventuelles exceptions levées par cette méthode. De plus, il n'est pas sûr pour les threads, vous devez créer startet stopsynchroniser des méthodes.
Monsieur Smith
2
Modifié jusqu'à la partie validation de l'argument, car je n'ai pas Eclipse ici pour tester le code. Merci pour les commentaires! Est-ce que c'est ce que vous vouliez dire? StartUpdates et stopUpdates synchronisés et placez un appel Looper.getMainLooper () à l'intérieur du constructeur du gestionnaire (j'espère que vous pouvez l'appeler directement à partir de la déclaration de champ)
ravemir
2
Je comprends ceci: error: call to this must be first statement in constructoril existe peut - être une solution simple.
msysmilu
4
Vote en faveur de l'importation - prend du temps pour comprendre d'où vient Handler lors de la programmation en Java avec désinvolture
Roman Susi
23

Je pense que la nouveauté est d'utiliser un ScheduledThreadPoolExecutor . Ainsi:

private final ScheduledThreadPoolExecutor executor_ = 
        new ScheduledThreadPoolExecutor(1);
this.executor_.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
    update();
    }
}, 0L, kPeriod, kTimeUnit);
Nels Beckman
la source
Executors.newSingleThreadScheduledExecutor()peut être une autre option ici.
Gulshan
13

La minuterie fonctionne bien. Ici, j'utilise Timer pour rechercher du texte après 1.5s et mettre à jour l'interface utilisateur. J'espère que cela pourra aider.

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);
Kai Wang
la source
où avez-vous mis l'intervalle?
Nathiel Barros
1
Salut Nathiel, je viens de mettre à jour mon message, j'espère que ça aide! Le temps d'intervalle est le deuxième paramètre de Timer.schedule ().
Kai Wang
7

Il y a 3 façons de procéder:

Utiliser ScheduledThreadPoolExecutor

Un peu exagéré car vous n'avez pas besoin d'un pool de Thread

   //----------------------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();
    }

Utiliser la tâche du minuteur

Ancien style Android

    //----------------------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();
    }

Utilisez Handler et Runnable

Style Android moderne

    //----------------------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);
    }

Gestionnaire non-fuite avec activité / contexte

Déclarez une classe Handler interne qui ne fuit pas de mémoire dans votre classe Activity / Fragment

/**
     * 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) {
                // ...
            }
        }
    }

Déclarez un exécutable qui effectuera votre tâche répétitive dans votre classe Activité / Fragment

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

         //DO YOUR THINGS
        }
    };

Initialiser l'objet Handler dans votre activité / fragment (ici FlashActivity est ma classe d'activité)

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

Pour répéter une tâche après un intervalle de temps fixe

taskHandler.postDelayed (repeatativeTaskRunnable, DELAY_MILLIS);

Pour arrêter la répétition de la tâche

taskHandler .removeCallbacks (repeatativeTaskRunnable);

MISE À JOUR: À 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);
    }
Hitesh Sahu
la source
6

La minuterie est une autre façon de faire votre travail, mais assurez-vous d'ajouter runOnUiThreadsi vous travaillez avec l'interface utilisateur.

    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);
    }});
  }

 }

}

et xml est ...

<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"/>

Une autre façon d'utiliser CountDownTimer

new CountDownTimer(30000, 1000) {

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

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

Planifiez un compte à rebours jusqu'à une date ultérieure, avec des notifications régulières à intervalles réguliers. Exemple d'affichage d'un compte à rebours de 30 secondes dans un champ de texte:

Pour plus de détails

Zar E Ahmer
la source
1
Le gestionnaire est préféré à la minuterie. Voir Timer vs Handler
Suragch
4

Essayez l'exemple suivant, cela fonctionne !!!

Utilisez [Handler] dans la méthode onCreate () qui utilise la méthode postDelayed () qui provoque l'ajout de Runnable à la file d'attente des messages, à exécuter après la durée spécifiée qui est de 0 dans l'exemple donné. 1

Référez-vous à ce 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);
        }
};

Gazal Patel
la source
4

Sur la base du post ci-dessus concernant le ScheduledThreadPoolExecutor , j'ai trouvé un utilitaire qui correspondait à mes besoins (je voulais lancer une méthode toutes les 3 secondes):

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);
    }
}
HelloImKevo
la source
4

Dans mon cas, j'ai dû exécuter un processus si l'une de ces conditions était vraie: si un processus précédent était terminé ou si 5 secondes s'étaient déjà écoulées. J'ai donc fait ce qui suit et travaillé plutôt bien:

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);
    }


}

Si process1 est lu, il exécute process2. Sinon, il incrémente les temps variables et rend le gestionnaire exécuté après une seconde. Il maintient une boucle jusqu'à ce que process1 soit lu ou que times soit 5. Lorsque times est égal à 5, cela signifie que 5 secondes se sont écoulées et à chaque seconde, la clause if de process1.isRead () est exécutée.

jvdecamargo
la source
1

En utilisant kotlin et sa Coroutine, c'est assez facile, déclarez d'abord un travail dans votre classe (mieux dans votre viewModel) comme ceci:

private var repeatableJob: Job? = null

puis quand vous voulez créer et démarrer, faites ceci:

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

et si vous voulez le terminer:

repeatableJob?.cancel()

PS: viewModelScope n'est disponible que dans les modèles de vue, vous pouvez utiliser d'autres étendues Coroutine telles quewithContext(Dispatchers.IO)

Plus d'informations: ici

Amin Keshavarzian
la source
0

Pour les personnes utilisant Kotlin, la réponse d'inazaruk ne fonctionnera pas, l'EDI nécessitera l'initialisation de la variable, donc au lieu d'utiliser l' postDelayedintérieur de Runnable, nous l'utiliserons dans une méthode distincte.

  • Initialisez votre Runnablecomme ceci:

    private var myRunnable = Runnable {
        //Do some work
        //Magic happens here ↓
        runDelayedHandler(1000)   }
  • Initialisez votre runDelayedHandlerméthode comme ceci:

     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)
        }
    }
  • Comme vous pouvez le voir, cette approche vous permettra de contrôler la durée de vie de la tâche, de la suivre keepRunninget de la modifier pendant la durée de vie de l'application fera le travail pour vous.

Tamim Attafi
la source