¿Cómo ejecutar un hilo Runnable en Android a intervalos definidos?

351

Desarrollé una aplicación para mostrar texto a intervalos definidos en la pantalla del emulador de Android. Estoy usando la Handlerclase Aquí hay un fragmento de mi código:

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

Cuando ejecuto esta aplicación, el texto se muestra solo una vez. ¿Por qué?

Rajapandian
fuente
109
Nunca recuerdo cómo hacer una carrera, así que siempre visito tu publicación sobre cómo hacerlo :))
Adrian Sicaru
2
jaja mismo aquí compañero tan cierto
NoXSaeeD
1
las lambdas son el camino a seguir ahora la mayor parte del tiempo;)
Xerus
@AdrianSicaru: lo mismo
Sovandara LENG

Respuestas:

534

La solución simple a su ejemplo es:

handler = new Handler();

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

handler.postDelayed(r, 1000);

O podemos usar hilo normal, por ejemplo (con Runner original):

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

thread.start();

Puede considerar su objeto ejecutable solo como un comando que se puede enviar a la cola de mensajes para su ejecución, y el controlador como solo un objeto auxiliar utilizado para enviar ese comando.

Más detalles están aquí http://developer.android.com/reference/android/os/Handler.html

alex2k8
fuente
Alex, tengo una pequeña duda. ¿Ahora el hilo funciona perfectamente y muestra el texto continuamente, si quiero detener esto significa lo que tengo que hacer? Por favor, ayúdame.
Rajapandian
11
Puede definir la variable booleana _stop y establecerla como 'verdadera' cuando desee detenerla. Y cambie 'while (verdadero)' en 'while (! _ Stop)', o si se utilizó la primera muestra, simplemente cambie a 'if (! _ Stop) handler.postDelayed (this, 1000)'.
alex2k8
¿Qué pasa si quiero reiniciar el mensaje?
Sonhja
y si necesito un runnable para configurar 8 ImageViews diferentes visibles una tras otra, luego configurarlas todas invisibles de la misma manera y así sucesivamente (para crear una animación "parpadeante"), ¿cómo puedo hacer eso?
Droidman
1
Si desea asegurarse de que el controlador se adjuntará al hilo principal, debe inicializarlo así: controlador = nuevo controlador (Looper.getMainLooper ());
Yair Kukielka
47
new Handler().postDelayed(new Runnable() {
    public void run() {
        // do something...              
    }
}, 100);
usuario2212515
fuente
2
Si desea asegurarse de que el controlador se adjuntará al hilo principal, debe inicializarlo así: nuevo controlador (Looper.getMainLooper ());
Yair Kukielka
1
¿No es esta solución equivalente a la publicación original? Solo ejecutaría Runnable una vez después de 100 milisegundos.
tronman
¡La respuesta de @YairKukielka es la solución! necesita adjuntar MainLooper. ¡Qué salvador de vida!
Houssem Chlegou
40

Creo que puede mejorar la primera solución de Alex2k8 para la actualización correcta cada segundo

1.código original:

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

2.Análisis

  • En el costo anterior, suponga que el tv.append("Hello Word")costo T milisegundos, después de mostrar 500 veces el tiempo de retraso es 500 * T milisegundos
  • Aumentará retrasado cuando se ejecuta mucho tiempo

3. Solución

Para evitar eso Simplemente cambie el orden de postDelayed (), para evitar retrasos:

public void run() {
    handler.postDelayed(this, 1000);
    tv.append("Hello World");
}
NguyenDat
fuente
66
-1 está asumiendo que la tarea que realiza en la ejecución () es un costo constante en cada ejecución, si esta fuera una operación en datos dinámicos (que generalmente es), terminaría con más de una ejecución () ocurriendo en una vez. Esta es la razón por la cual postDelayed generalmente se coloca al final.
Jay
1
@ Jay Desafortunadamente estás equivocado. Un controlador está asociado con un único subproceso (y un Looper, que es el método de ejecución de ese subproceso) + un MessageQueue. Cada vez que publica un mensaje, lo coloca en la cola y la próxima vez que el looper verifica la cola, ejecuta el método de ejecución del Runnable que publicó. Como todo esto sucede en un solo hilo, no puede tener más de 1 ejecutado al mismo tiempo. También hacer el postDelayed primero lo acercará a 1000ms por ejecución porque internamente usa el tiempo actual + 1000 como tiempo de ejecución. Si coloca el código antes de la publicación, agrega demora adicional.
zapl
1
@zapl gracias por el consejo sobre el controlador, supuse que estaría ejecutando múltiples ejecutables y, por lo tanto, múltiples hilos. Sin embargo, internamente, una condición como si ((currenttime - lastruntime)> 1000) funcionará bien cuando las duraciones de ejecución sean menores o iguales a 1000ms, sin embargo, cuando se excede, seguramente el temporizador se producirá a intervalos no lineales depende completamente del tiempo de ejecución del método de ejecución (de ahí mi punto sobre gastos computacionales impredecibles)
Jay
Si desea un período fijo, sin conflicto, mida la hora de inicio antes de hacer el trabajo y ajuste su retraso según corresponda. Aún verá un poco de latencia si la CPU está ocupada, pero puede permitirle un período más estricto y detectar si el sistema está sobrecargado (tal vez para indicar que las cosas de baja prioridad retroceden).
Ajax
27

Para repetir la tarea, puede usar

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

llámalo como

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

            }
        },500,1000);

El código anterior se ejecutará por primera vez después de medio segundo (500) y se repetirá después de cada segundo (1000)

Dónde

tarea siendo el método a ejecutar

después del tiempo de ejecución inicial

( Intervalo de tiempo para repetir la ejecución)

En segundo lugar

Y también puede usar CountDownTimer si desea ejecutar una Tarea varias veces.

    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

Y también puedes hacerlo con runnable. crear un método ejecutable como

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

        }
    };

Y llámalo de ambas maneras

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

O

new Thread(runnable).start();//to work in Background 
Zar E Ahmer
fuente
Para la opción # 3, ¿cómo puedo pausar / reanudar y también detenerme permanentemente?
Si8
cree una instancia de Handler como Handler handler = new Handler () y elimínela como handler.removeCallbacksAndMessages (null);
Zar E Ahmer
24

Creo que para este caso típico, es decir, ejecutar algo con un intervalo fijo, Timeres más apropiado. Aquí hay un ejemplo 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

Usar Timertiene pocas ventajas:

  • El retraso inicial y el intervalo se pueden especificar fácilmente en los scheduleargumentos de la función
  • El temporizador se puede detener simplemente llamando myTimer.cancel()
  • Si desea tener un solo hilo en ejecución, recuerde llamar myTimer.cancel() antes de programar uno nuevo (si myTimer no es nulo)
iTech
fuente
77
No creo que un temporizador sea más apropiado ya que no considera el ciclo de vida de Android. Cuando pausa y reanuda, no hay garantía de que el temporizador se ejecute correctamente. Yo diría que un runnable es la mejor opción.
Janpan
1
¿Eso significa que cuando una aplicación se pone en segundo plano se pausará un controlador? y cuando recupere el enfoque continuará (más o menos) como si nada hubiera pasado?
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
fuente
55
Esto debería dar un error. En su segunda línea está llamando variable rque está definida todavía.
Seúl
Si desea asegurarse de que el controlador se adjuntará al subproceso principal, debe inicializarlo así: controlador = nuevo controlador (Looper.getMainLooper ());
Yair Kukielka
solo respuesta repetitiva!
Hamid
¿Cómo puedo pausar / reanudar la ejecución con un clic de vista de imagen?
Si8
4

Si entiendo correctamente la documentación del método Handler.post ():

Hace que el Runnable r se agregue a la cola de mensajes. El ejecutable se ejecutará en el hilo al que está conectado este controlador.

Entonces, los ejemplos proporcionados por @ alex2k8, aunque funcionan correctamente, no son los mismos. En caso de que Handler.post()se use, no se crean nuevos hilos . Simplemente publica Runnableen el hilo Handlerpara que EDT lo ejecute . Después de eso, EDT solo se ejecuta Runnable.run(), nada más.

Recuerde: Runnable != Thread.

Damian Walczak
fuente
1
Eso es cierto. No cree un nuevo hilo cada vez, nunca. El objetivo de Handler y otros grupos de ejecución es hacer que uno o dos subprocesos retiren las tareas de una cola, para evitar la creación de subprocesos y GC. Si tiene una aplicación realmente con fugas, el GC adicional podría ayudar a cubrir situaciones de OutOfMemory, pero la mejor solución en ambos casos es evitar crear más trabajo del que necesita.
Ajax
Entonces, ¿la mejor manera de hacerlo es usando el hilo normal basado en la respuesta de 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
fuente
1

Un ejemplo interesante es que puedes ver continuamente un contador / cronómetro corriendo en un hilo separado. También muestra la ubicación GPS. Mientras que la actividad principal User Thread ya está allí.

Extracto:

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

Para ver el código ver aquí:

Ejemplo de subproceso que muestra la ubicación GPS y la hora actual ejecutables junto con el subproceso de interfaz de usuario de la actividad principal

Animesh Shrivastav
fuente
1
Sugerencia: si desea que su respuesta sea útil, aprenda a formatear la entrada aquí. Esa ventana de vista previa existe por una razón.
GhostCat
0

ahora en Kotlin puedes ejecutar hilos de esta manera:

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
fuente
0

Kotlin con corutinas

En Kotlin, usando corutinas puede hacer lo siguiente:

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

Pruébalo aquí !

Willi Mentzel
fuente