Comment exécuter un thread Runnable dans Android à des intervalles définis?

351

J'ai développé une application pour afficher du texte à des intervalles définis dans l'écran de l'émulateur Android. J'utilise la Handlerclasse. Voici un extrait de mon code:

handler = new Handler();
Runnable r = new Runnable() {
    public void run() {
        tv.append("Hello World");               
    }
};
handler.postDelayed(r, 1000);

Lorsque j'exécute cette application, le texte ne s'affiche qu'une seule fois. Pourquoi?

Rajapandian
la source
109
Je ne me souviens jamais comment faire un runnable, donc je visite toujours votre message sur la façon de le faire :))
Adrian Sicaru
2
haha même ici mate so true
NoXSaeeD
1
les lambdas sont le chemin à parcourir maintenant la plupart du temps;)
Xerus
@AdrianSicaru: même chose
Sovandara LENG

Réponses:

535

La solution simple à votre exemple est:

handler = new Handler();

final Runnable r = new Runnable() {
    public void run() {
        tv.append("Hello World");
        handler.postDelayed(this, 1000);
    }
};

handler.postDelayed(r, 1000);

Ou nous pouvons utiliser un fil normal par exemple (avec le Runner d'origine):

Thread thread = new Thread() {
    @Override
    public void run() {
        try {
            while(true) {
                sleep(1000);
                handler.post(this);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};

thread.start();

Vous pouvez considérer votre objet exécutable comme une commande qui peut être envoyée à la file d'attente de messages pour exécution et le gestionnaire comme un simple objet d'aide utilisé pour envoyer cette commande.

Plus de détails sont ici http://developer.android.com/reference/android/os/Handler.html

alex2k8
la source
Alex, j'ai un petit doute.Maintenant, le fil fonctionne parfaitement et affiche le texte en continu, si je veux arrêter, cela signifie ce que je dois faire? S'il vous plaît, aidez-moi.
Rajapandian
11
Vous pouvez définir la variable booléenne _stop et la définir sur "true" lorsque vous souhaitez arrêter. Et changez 'while (true)' en 'while (! _ Stop)', ou si le premier échantillon utilisé, changez simplement en 'if (! _ Stop) handler.postDelayed (this, 1000)'.
alex2k8
Et si je veux redémarrer le message?
Sonhja
et si j'ai besoin d'un exécutable pour définir 8 images différentes, visibles les unes après les autres, puis les rendre toutes invisibles de la même manière et ainsi de suite (pour créer une animation "clignotante"), comment faire?
Droidman
1
Si vous voulez être sûr que Handler sera attaché au thread principal, vous devez l'initialiser comme ceci: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka
47
new Handler().postDelayed(new Runnable() {
    public void run() {
        // do something...              
    }
}, 100);
user2212515
la source
2
Si vous voulez être sûr que Handler sera attaché au thread principal, vous devez l'initialiser comme ceci: new Handler (Looper.getMainLooper ());
Yair Kukielka
1
Cette solution n'est-elle pas équivalente à la publication d'origine? Il n'exécuterait la Runnable qu'une fois après 100 millisecondes.
tronman
@YairKukielka est la solution! vous devez attacher MainLooper. un tel sauveur de vie!
Houssem Chlegou
40

Je pense que peut améliorer la première solution d'Alex2k8 pour une mise à jour correcte chaque seconde

1. code d'origine:

public void run() {
    tv.append("Hello World");
    handler.postDelayed(this, 1000);
}

2. analyse

  • Dans le coût ci-dessus, supposez le tv.append("Hello Word")coût T millisecondes, après l'affichage, 500 fois le temps différé est de 500 * T millisecondes
  • Il augmentera retardé lors d'une exécution de longue durée

3. Solution

Pour éviter cela, il suffit de changer l'ordre de postDelayed (), pour éviter les retards:

public void run() {
    handler.postDelayed(this, 1000);
    tv.append("Hello World");
}
NguyenDat
la source
6
-1 vous supposez que la tâche que vous effectuez dans l'exécution () est un coût constant à chaque exécution, s'il s'agissait d'une opération sur des données dynamiques (ce qui est généralement le cas), vous vous retrouveriez avec plus d'une exécution () survenant à une fois que. C'est pourquoi postDelayed est généralement placé à la fin.
Jay
1
@Jay Malheureusement, vous vous trompez. Un gestionnaire est associé à un seul thread (et un boucleur qui est la méthode d'exécution de ce thread) + un MessageQueue. Chaque fois que vous publiez un message, vous le mettez en file d'attente et la prochaine fois que le boucleur vérifie la file d'attente, il exécute la méthode d'exécution du Runnable que vous avez publié. Étant donné que tout cela se produit dans un seul thread, vous ne pouvez pas en exécuter plus d'un en même temps. Faire également le postDelayed en premier vous rapprochera de 1000 ms par exécution car en interne, il utilise l'heure actuelle + 1000 comme temps d'exécution. Si vous mettez du code avant la publication, vous ajoutez un délai supplémentaire.
zapl
1
@zapl merci pour l'astuce sur le gestionnaire, j'ai supposé qu'il exécuterait plusieurs runnables et donc plusieurs threads. En interne cependant, une condition telle que if ((currenttime - lastruntime)> 1000) fonctionnera correctement lorsque les durées d'exécution sont inférieures ou égales à 1000 ms, cependant, lorsque cela est dépassé, le temporisateur va sûrement se produire à des intervalles non linéaires dépendant entièrement du temps d'exécution de la méthode d'exécution (d'où mon point sur les dépenses de calcul imprévisibles)
Jay
Si vous souhaitez une période fixe, sans conflit, mesurez l'heure de début avant de travailler et ajustez votre délai en conséquence. Vous verrez toujours un peu de latence si le processeur est occupé, mais cela peut vous permettre une période plus stricte et détecter si le système est surchargé (peut-être pour signaler des choses de faible priorité à arrêter).
Ajax
27

Pour répéter la tâche, vous pouvez utiliser

new Timer().scheduleAtFixedRate(task, runAfterADelayForFirstTime, repeaingTimeInterval);

appelle ça comme

new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {

            }
        },500,1000);

Le code ci-dessus s'exécutera la première fois après une demi-seconde (500) et se répétera après chaque seconde (1000)

tâche étant la méthode à exécuter

après le temps de l'exécution initiale

( intervalle de temps pour répéter l'exécution)

Deuxièmement

Et vous pouvez également utiliser CountDownTimer si vous souhaitez exécuter un nombre de tâches plusieurs fois.

    new CountDownTimer(40000, 1000) { //40000 milli seconds is total time, 1000 milli seconds is time interval

     public void onTick(long millisUntilFinished) {
      }
      public void onFinish() {
     }
    }.start();

//Above codes run 40 times after each second

Et vous pouvez également le faire avec runnable. créer une méthode exécutable comme

Runnable runnable = new Runnable()
    {
        @Override
        public void run()
        {

        }
    };

Et appelez-le de ces deux façons

new Handler().postDelayed(runnable, 500 );//where 500 is delayMillis  // to work on mainThread

OU

new Thread(runnable).start();//to work in Background 
Zar E Ahmer
la source
Pour l'option n ° 3, comment puis-je suspendre / reprendre et m'arrêter définitivement?
Si8
créer une instance de Handler comme Handler handler = new Handler () et la supprimer comme handler.removeCallbacksAndMessages (null);
Zar E Ahmer
24

Je crois que pour ce cas typique, c'est-à-dire pour exécuter quelque chose avec un intervalle fixe, Timerest plus approprié. Voici un exemple simple:

myTimer = new Timer();
myTimer.schedule(new TimerTask() {          
@Override
public void run() {
    // If you want to modify a view in your Activity
    MyActivity.this.runOnUiThread(new Runnable()
        public void run(){
            tv.append("Hello World");
        });
    }
}, 1000, 1000); // initial delay 1 second, interval 1 second

L'utilisation Timerprésente peu d'avantages:

  • Le délai initial et l'intervalle peuvent être facilement spécifiés dans les schedulearguments de la fonction
  • Le chronomètre peut être arrêté en appelant simplement myTimer.cancel()
  • Si vous souhaitez avoir un seul thread en cours d'exécution, n'oubliez pas d'appeler myTimer.cancel() avant d'en planifier un nouveau (si myTimer n'est pas nul)
iTech
la source
7
Je ne crois pas qu'une minuterie soit plus appropriée car elle ne prend pas en compte le cycle de vie d'Android. Lorsque vous vous arrêtez et reprenez, rien ne garantit que le chronomètre fonctionnera correctement. Je dirais qu'un runnable est le meilleur choix.
Janpan
1
Cela signifie-t-il que lorsqu'une application est mise en arrière-plan, un gestionnaire est suspendu? et quand il retrouvera sa concentration, il continuera (plus ou moins) comme si de rien n'était?
Andrew Gallasch
17
Handler handler=new Handler();
Runnable r = new Runnable(){
    public void run() {
        tv.append("Hello World");                       
        handler.postDelayed(r, 1000);
    }
}; 
handler.post(r);
singh arjun
la source
5
Cela devrait donner une erreur. Dans votre deuxième ligne, vous appelez une variable rqui est encore définie.
Séoul
Si vous voulez être sûr que Handler sera attaché au thread principal, vous devez l'initialiser comme ceci: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka
réponse juste répétitive!
Hamid
Comment puis-je suspendre / reprendre le runnable avec un clic de visualisation d'image?
Si8
4

Si je comprends bien la documentation de la méthode Handler.post ():

Provoque l'ajout de Runnable r à la file d'attente de messages. L'exécutable sera exécuté sur le thread auquel ce gestionnaire est attaché.

Donc, les exemples fournis par @ alex2k8, même s'ils fonctionnent correctement, ne sont pas les mêmes. Dans le cas, où Handler.post()est utilisé, aucun nouveau thread n'est créé . Vous venez de publier Runnablesur le fil avec Handlerpour être exécuté par EDT . Après cela, EDT n'exécute que Runnable.run()rien d'autre.

Rappelez - vous: Runnable != Thread.

Damian Walczak
la source
1
Vrai que. Ne créez pas de nouveau fil à chaque fois, jamais. L'intérêt du gestionnaire et des autres pools d'exécution est d'avoir un ou deux threads pour extraire les tâches d'une file d'attente, afin d'éviter la création de threads et le GC. Si vous avez une application qui fuit vraiment, le GC supplémentaire peut aider à couvrir les situations OutOfMemory, mais la meilleure solution dans les deux cas est d'éviter de créer plus de travail que nécessaire.
Ajax
Donc, la meilleure façon de le faire est d'utiliser le thread normal basé sur la réponse d'Alex2K8?
Compaq LE2202x
4

Kotlin

private lateinit var runnable: Runnable
override fun onCreate(savedInstanceState: Bundle?) {
    val handler = Handler()
    runnable = Runnable {
        // do your work
        handler.postDelayed(runnable, 2000)
    }
    handler.postDelayed(runnable, 2000)
}

Java

Runnable runnable;
Handler handler;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    handler = new Handler();
    runnable = new Runnable() {
        @Override
        public void run() {
            // do your work
            handler.postDelayed(this, 1000);
        }
    };
    handler.postDelayed(runnable, 1000);
}
Khemraj
la source
1

Un exemple intéressant est que vous pouvez voir en continu un compteur / chronomètre fonctionnant dans un thread séparé. Affiche également la position GPS. Alors que l'activité principale Thread d'interface utilisateur est déjà là.

Extrait:

try {    
    cnt++; scnt++;
    now=System.currentTimeMillis();
    r=rand.nextInt(6); r++;    
    loc=lm.getLastKnownLocation(best);    

    if(loc!=null) { 
        lat=loc.getLatitude();
        lng=loc.getLongitude(); 
    }    

    Thread.sleep(100); 
    handler.sendMessage(handler.obtainMessage());
} catch (InterruptedException e) {   
    Toast.makeText(this, "Error="+e.toString(), Toast.LENGTH_LONG).show();
}

Pour voir le code, voir ici:

Exemple de thread affichant la position GPS et l'heure courante avec le thread d'interface utilisateur de l'activité principale

Animesh Shrivastav
la source
1
Astuce: si vous souhaitez rendre votre réponse utile - découvrez comment formater l'entrée ici. Cette fenêtre d' aperçu existe pour une raison.
GhostCat
0

maintenant dans Kotlin, vous pouvez exécuter des threads de cette façon:

class SimpleRunnable: Runnable {
    public override fun run() {
        println("${Thread.currentThread()} has run.")
    }
}
fun main(args: Array<String>) {
    val thread = SimpleThread()
    thread.start() // Will output: Thread[Thread-0,5,main] has run.
    val runnable = SimpleRunnable()
    val thread1 = Thread(runnable)
    thread1.start() // Will output: Thread[Thread-1,5,main] has run
}
André Abboud
la source
0

Kotlin avec Coroutines

Dans Kotlin, en utilisant des coroutines, vous pouvez effectuer les opérations suivantes:

CoroutineScope(Dispatchers.Main).launch { // Main, because UI is changed
    ticker(delayMillis = 1000, initialDelayMillis = 1000).consumeEach {
        tv.append("Hello World")
    }
}

Essayez-le ici !

Willi Mentzel
la source