Comment mettre en pause / dormir le fil ou le processus dans Android?

294

Je veux faire une pause entre deux lignes de code, laissez-moi vous expliquer un peu:

-> l'utilisateur clique sur un bouton (une carte en fait) et je le montre en changeant l'arrière-plan de ce bouton:

thisbutton.setBackgroundResource(R.drawable.icon);

-> après disons 1 seconde, je dois revenir à l'état précédent du bouton en modifiant son arrière-plan:

thisbutton.setBackgroundResource(R.drawable.defaultcard);

-> J'ai essayé de suspendre le thread entre ces deux lignes de code avec:

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

Cependant, cela ne fonctionne pas. C'est peut-être le processus et non le fil que je dois interrompre?

J'ai aussi essayé (mais ça ne marche pas):

new Reminder(5);

Avec ça:

public class Reminder {

Timer timer;

        public Reminder(int seconds) {
            timer = new Timer();
            timer.schedule(new RemindTask(), seconds*1000);
        }

        class RemindTask extends TimerTask {
            public void run() {
                System.out.format("Time's up!%n");
                timer.cancel(); //Terminate the timer thread
            }
        }  
    }

Comment puis-je suspendre / dormir le fil ou le processus?

Hubert
la source
4
Oh, utilisez simplement le bloc de pause de thread classique: while (true) {}
KristoferA
6
@ KristoferA-Huagati.com Je ne sais pas si vous êtes sarcastique ou bien il y a un peu de magie Dalvik / Android pour que cela soit acceptable sur Android. Pouvez-vous clarifier s'il vous plait? Désolé pour le doute mais je demande parce que tout (!conditionCheck()) {}est généralement découragé.
Variable misérable
"Cependant, cela ne fonctionne pas." "J'ai aussi essayé (mais ça ne marche pas)" C'est un exemple classique de dire qu'il y a un problème sans donner les symptômes. En quoi ces tentatives n'ont-elles pas répondu à vos besoins? Le fil ne s'est-il pas arrêté? As-tu eu un message d'erreur?
LarsH

Réponses:

454

Une solution à ce problème consiste à utiliser la méthode Handler.postDelayed () . Certains supports de formation Google suggèrent la même solution.

@Override
public void onClick(View v) {
    my_button.setBackgroundResource(R.drawable.icon);

    Handler handler = new Handler(); 
    handler.postDelayed(new Runnable() {
         @Override 
         public void run() { 
              my_button.setBackgroundResource(R.drawable.defaultcard); 
         } 
    }, 2000); 
}

Cependant, certains ont souligné que la solution ci-dessus provoque une fuite de mémoire car elle utilise une classe interne et anonyme non statique qui contient implicitement une référence à sa classe externe, l'activité. Il s'agit d'un problème lorsque le contexte d'activité est récupéré.

Une solution plus complexe qui évite la fuite de mémoire sous-classe les Handleret Runnableavec les classes internes statiques à l'intérieur de l'activité car les classes internes statiques ne contiennent pas de référence implicite à leur classe externe:

private static class MyHandler extends Handler {}
private final MyHandler mHandler = new MyHandler();

public static class MyRunnable implements Runnable {
    private final WeakReference<Activity> mActivity;

    public MyRunnable(Activity activity) {
        mActivity = new WeakReference<>(activity);
    }

    @Override
    public void run() {
        Activity activity = mActivity.get();
        if (activity != null) {
            Button btn = (Button) activity.findViewById(R.id.button);
            btn.setBackgroundResource(R.drawable.defaultcard);
        }
    }
}

private MyRunnable mRunnable = new MyRunnable(this);

public void onClick(View view) {
    my_button.setBackgroundResource(R.drawable.icon);

    // Execute the Runnable in 2 seconds
    mHandler.postDelayed(mRunnable, 2000);
}

Notez que le Runnableutilise une WeakReference à l'activité, qui est nécessaire dans une classe statique qui a besoin d'accéder à l'interface utilisateur.

tronman
la source
3
Cela fonctionne, mais c'est un moyen assez peu pratique d'introduire des retards dans le code, non?
Ehtesh Choudhury
4
Je ne sais pas ce que vous entendez par "incommode". La méthode postDelayed du gestionnaire est conçue pour indiquer à Android que vous voulez qu'un peu de code soit exécuté après un certain temps.
tronman
27
après 2 ans et ce code m'a juste aidé! merci @tronman !! :)
Melvin Lai
2
Vous pouvez simplement le copier dans une autre variable (finale) comme ça final Button mynewbutton = mybutton;et l'utiliser mynewbuttondans le gestionnaire et le Runnable à partir de là.
Dzhuneyt
2
@MelvinLai après 5 ans et ce code m'a juste aidé! :)
gabrieloliveira
191

Vous pouvez essayer celui-ci il est court

SystemClock.sleep(7000);

AVERTISSEMENT : ne faites jamais cela sur un thread d'interface utilisateur.

Utilisez-le pour dormir, par exemple. fil de fond.


La solution complète pour votre problème sera: Ceci est disponible API 1

findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View button) {
                button.setBackgroundResource(R.drawable.avatar_dead);
                final long changeTime = 1000L;
                button.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        button.setBackgroundResource(R.drawable.avatar_small);
                    }
                }, changeTime);
            }
        });

Sans créer de gestionnaire tmp. Cette solution est également meilleure que @tronman car nous ne conservons pas la vue par le gestionnaire. Nous n'avons pas non plus de problème avec le gestionnaire créé sur un mauvais thread;)

Documentation

sommeil public vide statique (long ms)

Ajouté au niveau 1 de l'API

Attend un nombre donné de millisecondes (de uptimeMillis) avant de revenir. Similaire au sommeil (long), mais ne lève pas InterruptedException ; Les événements interrupt () sont différés jusqu'à la prochaine opération interruptible. Ne revient pas tant qu'au moins le nombre de millisecondes spécifié ne s'est pas écoulé.

Paramètres

ms pour dormir avant de revenir, en millisecondes de disponibilité.

Code pour la classe postDelayed from View:

/**
 * <p>Causes the Runnable to be added to the message queue, to be run
 * after the specified amount of time elapses.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 * @param delayMillis The delay (in milliseconds) until the Runnable
 *        will be executed.
 *
 * @return true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the Runnable will be processed --
 *         if the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 *
 * @see #post
 * @see #removeCallbacks
 */
public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
    return true;
}
Dawid Drozd
la source
22
Et laissez-vous geler l'interface utilisateur Android? Ne fais pas ça.
shkschneider
3
Ctrl + Maj + O (importation automatique Eclipse)
Dawid Drozd
13
Gelldur, vous manquez le point du commentaire de @ RichieHH. Par rapport à la réponse qui a été acceptée 3 ans avant votre publication , ce que vous proposez n'aide pas OP à résoudre son problème. Motif: Le code, à la fois comme indiqué par OP et comme indiqué dans la réponse acceptée, s'exécute dans un gestionnaire d'interface utilisateur . Dire OMG of course do this in background threadn'est pas pertinent, sauf si vous montrez COMMENT le mettre en arrière-plan. À ce moment, vous découvrirez que vous avez une réponse plus compliquée que la réponse déjà acceptée. Ai-je mentionné qu'une meilleure solution a été acceptée il y a trois ans? : P
ToolmakerSteve
2
... oublié de mentionner que ce qui rend cette suggestion particulièrement hors de propos pour la question, c'est que OP veut explicitement faire une action d'interface utilisateur après le retard. Donc, vous auriez une solution dans laquelle vous avez créé un thread d'arrière-plan (déjà plus lourd que nécessaire pour résoudre le problème), dormi, puis vous devez (en quelque sorte) revenir au thread d'interface utilisateur. Handler + postRunnableaccomplit tout cela, en une seule étape. Sans la surcharge du système de créer un deuxième thread.
ToolmakerSteve
2
@ToolmakerSteve heureux maintenant: D? La question principale est: "Comment mettre en pause / dormir le thread ou le processus dans Android?" donc ma réponse était simple :). Si quelqu'un google pour "comment dormir le fil", cette question s'affichera. Il / Elle cherche probablement ma réponse: P. Mais ok j'ai ajouté une réponse complète;)
Dawid Drozd
28

J'utilise ceci:

Thread closeActivity = new Thread(new Runnable() {
  @Override
  public void run() {
    try {
      Thread.sleep(3000);
      // Do some stuff
    } catch (Exception e) {
      e.getLocalizedMessage();
    }
  }
});
Byt3
la source
4
Que doit-on e.getLocalizedMessage()faire?
Philipp Reichart
j'utilise e.getLocalizedMessage () quand j'ai besoin d'un message d'exception simple, général et rapide
Byt3
1
Mais je parie que vous ne le faites pas dans la situation qu'OP demande, à l'intérieur d'une méthode de clic sur l'interface utilisateur, qui va apporter d'autres mises à jour à l'interface utilisateur. La Handler/postDelayedsolution acceptée présente deux avantages: (1) évite la surcharge système du 2e thread, (1) s'exécute sur le thread d'interface utilisateur, et peut donc effectuer des changements d'interface sans provoquer d'exception.
ToolmakerSteve
1
Pas directement lié à l'objectif principal de la question, mais vous ne devez pas intercepter l'exception. Attrapez plutôt InterruptedException. En attrapant Exception, vous attraperez tout ce qui pourrait mal tourner, cachant ainsi les problèmes que vous auriez autrement pris.
Herve Thu
16

Vous ne voulez probablement pas le faire de cette façon. En mettant un explicite sleep()dans votre gestionnaire d'événements cliqué sur un bouton, vous verrouillez réellement l'interface utilisateur entière pendant une seconde. Une alternative consiste à utiliser une sorte de minuterie à un coup . Créez une TimerTask pour redonner à la couleur d'arrière-plan la couleur par défaut et planifiez-la sur la minuterie.

Une autre possibilité consiste à utiliser un gestionnaire . Il y a un tutoriel sur quelqu'un qui est passé de l'utilisation d'une minuterie à l'utilisation d'un gestionnaire.

Soit dit en passant, vous ne pouvez pas suspendre un processus. Un processus Java (ou Android) a au moins 1 thread et vous ne pouvez mettre en veille que les threads.

Daniel Yankowsky
la source
14

J'utilise CountDownTime

new CountDownTimer(5000, 1000) {

    @Override
    public void onTick(long millisUntilFinished) {
        // do something after 1s
    }

    @Override
    public void onFinish() {
        // do something end times 5s
    }

}.start(); 
vudandroid
la source
c'est utile.
UzaySan
9

C'est ce que j'ai fait à la fin de la journée - fonctionne bien maintenant:

@Override
    public void onClick(View v) {
        my_button.setBackgroundResource(R.drawable.icon);
        // SLEEP 2 SECONDS HERE ...
        final Handler handler = new Handler(); 
        Timer t = new Timer(); 
        t.schedule(new TimerTask() { 
                public void run() { 
                        handler.post(new Runnable() { 
                                public void run() { 
                                 my_button.setBackgroundResource(R.drawable.defaultcard); 
                                } 
                        }); 
                } 
        }, 2000); 
    }
Hubert
la source
2
Pourquoi ne pas post-retardé? Aucune minuterie requise.
RichieHH
8

En plus des réponses de M. Yankowsky, vous pouvez également utiliser postDelayed(). Celui-ci est disponible sur tout View(par exemple, votre carte) et prend un Runnableet un délai Il exécute Runnableaprès ce délai.

CommonsWare
la source
5

Ceci est mon exemple

Créer un Java Utils

    import android.app.ProgressDialog;
    import android.content.Context;
    import android.content.Intent;

    public class Utils {

        public static void showDummyWaitingDialog(final Context context, final Intent startingIntent) {
            // ...
            final ProgressDialog progressDialog = ProgressDialog.show(context, "Please wait...", "Loading data ...", true);

            new Thread() {
                public void run() {
                    try{
                        // Do some work here
                        sleep(5000);
                    } catch (Exception e) {
                    }
                    // start next intent
                    new Thread() {
                        public void run() {
                        // Dismiss the Dialog 
                        progressDialog.dismiss();
                        // start selected activity
                        if ( startingIntent != null) context.startActivity(startingIntent);
                        }
                    }.start();
                }
            }.start();  

        }

    }    
Stefano
la source
3
Pourtant, une autre réponse ajoutée des années après que la question a déjà été bien résolue, qui n'est pas pertinente pour la question, car elle ne répond pas au désir déclaré de pouvoir effectuer un travail d'interface utilisateur. Vous ne pouvez pas faire fonctionner l'interface utilisateur ici, car vous exécutez un nouveau thread. Pas sur le thread d'interface utilisateur.
ToolmakerSteve
1
Du côté UX des choses, montrer une boîte de dialogue de blocage à l'utilisateur pour charger les données n'est ... pas bon.
2Dee
4

Ou vous pouvez utiliser:

android.os.SystemClock.sleep(checkEvery)

ce qui a l'avantage de ne pas nécessiter d'emballage try ... catch.

aaaa
la source
0

Si vous utilisez Kotlin et coroutines , vous pouvez simplement faire

GlobalScope.launch {
   delay(3000) // In ms
   //Code after sleep
}

Et si vous devez mettre à jour l'interface utilisateur

GlobalScope.launch {
  delay(3000)
  GlobalScope.launch(Dispatchers.Main) {
    //Action on UI thread
  }
}
Guillaume
la source
0

Je sais que c'est un vieux fil, mais dans la documentation Android j'ai trouvé une solution qui fonctionnait très bien pour moi ...

new CountDownTimer(30000, 1000) {

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

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

https://developer.android.com/reference/android/os/CountDownTimer.html

J'espère que cela aide quelqu'un ...

John Byrne
la source
0
  class MyActivity{
    private final Handler handler = new Handler();
    private Runnable yourRunnable;
    protected void onCreate(@Nullable Bundle savedInstanceState) {
       // ....
       this.yourRunnable = new Runnable() {
               @Override
               public void run() {
                   //code
               }
            };

        this.handler.postDelayed(this.yourRunnable, 2000);
       }


     @Override
  protected void onDestroy() {
      // to avoid memory leaks
      this.handler.removeCallbacks(this.yourRunnable);
      }
    }

Et pour être sûr que vous pouvez le combiner avec la méthode de la "classe statique" comme décrit dans la réponse tronman

débutant
la source