¿Repetir una tarea con un retraso de tiempo?

216

Tengo una variable en mi código que dice "estado".

Quiero mostrar algo de texto en la aplicación dependiendo de este valor variable. Esto debe hacerse con un retraso de tiempo específico.

Es como,

  • Comprobar el valor de la variable de estado

  • Mostrar texto

  • Espera 10 segundos

  • Comprobar el valor de la variable de estado

  • Mostrar texto

  • Espera 15 segundos

y así. El retraso de tiempo puede variar y se establece una vez que se muestra el texto.

Lo intenté Thread.sleep(time delay)y falló. ¿Alguna mejor manera de hacer esto?

Kalpa Pathum Welivitigoda
fuente

Respuestas:

448

Debes usar Handlerla postDelayedfunción para este propósito. Ejecutará su código con el retraso especificado en el hilo principal de la interfaz de usuario, por lo que podrá actualizar los controles de la interfaz de usuario.

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
fuente
1
Gracias inazaruk, conseguí que funcione. Encontró 2 v pequeños errores tipográficos (en la parte superior su "Handler" no "Handle" y en la parte inferior su "removeCallbacks" no remove "removecallback". Pero en cualquier caso el código era exactamente lo que estaba buscando. Intentando pensar lo que puedo hacer para devolver el favor. Al menos te has ganado mi respeto. Saludos
cordiales
20
Buen programa, funciona absolutamente bien. Pero el startRepeatingTask () tuvo que ser llamado desde el hilo del método / UI onCreate (¡me tomó un tiempo darme cuenta de esto!), Tal vez este punto podría haber sido mencionado en alguna parte. Saludos
gkris
1
tu respuesta sigue dando. Esto me ayudó a salir de un hoyo hoy. Gracias.
Dean Blakely
¿Hay alguna manera de tener un Runnable repetido dentro del método getView () de un adaptador?
toobsco42
1
Aquí, cuando importamos clases, ¿qué debemos importar? android.os.Handler o java.util.logging.Handler?
EJ Chathuranga
34

Para cualquier persona interesada, aquí hay una clase que creé usando el código de inazaruk que crea todo lo necesario (lo llamé UIUpdater porque lo uso para actualizar periódicamente la interfaz de usuario, pero puede llamarlo como quiera):

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

Luego puede crear un objeto UIUpdater dentro de su clase y usarlo así:

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

// Start updates
mUIUpdater.startUpdates();

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

Si desea usar esto como un actualizador de actividad, coloque la llamada de inicio dentro del método onResume () y la llamada de detención dentro de onPause (), para que las actualizaciones se inicien y detengan de acuerdo con la visibilidad de la actividad.

ravemir
fuente
1
Editado: UPDATE_INTERVAL = interval;debe estar antes this(uiUpdater); en UIUpdater(Runnable uiUpdater, int interval)(ya que el valor de UPDATE_INTERVALse usa y debe ser el que se pasa como parámetro interval;). También evite el ancho de más de 80 caracteres en el código cuando sea posible (casi siempre;)
Mr_and_Mrs_D
55
Esta clase tiene varios problemas. En primer lugar, debe crearse una instancia en el hilo principal para poder actualizar la GUI. Que podría haber resuelto este pasando la lanzadera principal al constructor manejador: new Handler(Looper.getMainLooper()). En segundo lugar, no valida los argumentos, por lo que se traga Runnables nulos e intervalos negativos. Finalmente, no tiene en cuenta el tiempo que pasa en la uiUpdater.run()línea, ni maneja posibles excepciones generadas por ese método. Además, no es hilo de seguridad, que debe hacer starty stopmétodos sincronizados.
Señor Smith
2
Editado hasta la parte de validación del argumento, ya que no tengo Eclipse aquí para probar el código. ¡Gracias por la respuesta! ¿Es esto lo que quisiste decir? StartUpdates y stopUpdates sincronizados y coloque una llamada Looper.getMainLooper () dentro del constructor Handler (espero que pueda llamarlo directamente desde la declaración de campo)
ravemir
2
Entiendo esto: error: call to this must be first statement in constructortal vez hay una solución fácil.
msysmilu
44
Votación por tener importación: toma tiempo descubrir de dónde viene Handler cuando se programa en Java de manera informal
Roman Susi
23

Creo que lo nuevo es usar un ScheduledThreadPoolExecutor . Al igual que:

private final ScheduledThreadPoolExecutor executor_ = 
        new ScheduledThreadPoolExecutor(1);
this.executor_.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
    update();
    }
}, 0L, kPeriod, kTimeUnit);
Nels Beckman
fuente
Executors.newSingleThreadScheduledExecutor()Puede ser otra opción aquí.
Gulshan
13

El temporizador funciona bien. Aquí, uso el temporizador para buscar texto después de 1.5s y actualizar la interfaz de usuario. Espero que ayude.

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
fuente
donde pusiste el intervalo de tiempo?
Nathiel Barros
1
Hola Nathiel, acabo de actualizar mi publicación, ¡espero que ayude! El tiempo de intervalo es el segundo parámetro de Timer.schedule ().
Kai Wang
7

Hay 3 formas de hacerlo:

Utilice ScheduledThreadPoolExecutor

Un poco exagerado ya que no necesitas un grupo de hilos

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

Usar tarea de temporizador

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

Use Handler y Runnable

Estilo moderno de Android

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

Controlador sin fugas con actividad / contexto

Declare una clase de controlador interno que no pierda memoria en su clase de actividad / fragmento

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

Declare un ejecutable que realizará su tarea repetitiva en su clase Actividad / Fragmento

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

         //DO YOUR THINGS
        }
    };

Inicialice el objeto Handler en su Activity / Fragment (aquí FlashActivity es mi clase de actividad)

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

Para repetir una tarea después del intervalo de tiempo fijo

taskHandler.postDelayed (repeatativeTaskRunnable, DELAY_MILLIS);

Para detener la repetición de la tarea.

taskHandler .removeCallbacks (repeatativeTaskRunnable);

ACTUALIZACIÓN: en 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
fuente
6

El temporizador es otra forma de hacer su trabajo, pero asegúrese de agregarlo runOnUiThreadsi está trabajando con la interfaz de usuario.

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

 }

}

y xml es ...

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

Otra forma de usar CountDownTimer

new CountDownTimer(30000, 1000) {

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

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

Programe una cuenta regresiva hasta un momento en el futuro, con notificaciones regulares en intervalos a lo largo del camino. Ejemplo de mostrar una cuenta regresiva de 30 segundos en un campo de texto:

Para detalles

Zar E Ahmer
fuente
1
Se prefiere el controlador sobre el temporizador. Ver Timer vs Handler
Suragch
4

¡Intenta seguir el ejemplo que funciona!

Use [Handler] en el método onCreate () que utiliza el método postDelayed () que hace que el Runnable se agregue a la cola de mensajes y se ejecute después de que transcurra el tiempo especificado que es 0 en el ejemplo dado. 1

Consulte este código:

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

Con base en la publicación anterior sobre el ScheduledThreadPoolExecutor , se me ocurrió una utilidad que se adaptaba a mis necesidades (quería activar un método cada 3 segundos):

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

En mi caso, tuve que ejecutar un proceso si una de estas condiciones era cierta: si se completó un proceso anterior o si ya habían pasado 5 segundos. Entonces, hice lo siguiente y funcionó bastante 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 se lee el proceso1, se ejecuta el proceso2. Si no, incrementa los tiempos variables y hace que el controlador se ejecute después de un segundo. Mantiene un ciclo hasta que se lee el proceso1 o los tiempos son 5. Cuando los tiempos son 5, significa que pasaron 5 segundos y en cada segundo, se ejecuta la cláusula if de process1.isRead ().

jvdecamargo
fuente
1

Usar kotlin y su Coroutine es bastante fácil, primero declare un trabajo en su clase (mejor en su modelo de vista) así:

private var repeatableJob: Job? = null

luego, cuando desee crear y comenzar, haga esto:

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

y si quieres terminarlo:

repeatableJob?.cancel()

PD: viewModelScopesolo está disponible en modelos de vista, puede usar otros ámbitos de Coroutine comowithContext(Dispatchers.IO)

Más información: aquí

Amin Keshavarzian
fuente
0

Para las personas que usan Kotlin, la respuesta de inazaruk no funcionará, el IDE requerirá que se inicialice la variable, por lo que en lugar de usar el postDelayedinterior Runnable, la usaremos en un método separado.

  • Inicialice Runnableasí:

    private var myRunnable = Runnable {
        //Do some work
        //Magic happens here ↓
        runDelayedHandler(1000)   }
  • Inicialice su runDelayedHandlermétodo de esta manera:

     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)
        }
    }
  • Como puede ver, este enfoque le permitirá controlar la vida útil de la tarea, realizar un seguimiento keepRunningy cambiarla durante la vida útil de la aplicación hará el trabajo por usted.

Tamim Attafi
fuente