Programación de tareas recurrentes en Android

122

Estoy diseñando una aplicación que tiene la tarea recurrente de enviar presencia a un servidor dedicado siempre que la aplicación esté en primer plano.

En mis búsquedas en la web, vi algunos enfoques diferentes y quería saber cuál es la mejor manera de hacerlo.

¿Cuál es la mejor forma de programar una llamada al servidor?

Las opciones que vi fueron:

  1. Temporizador .

  2. ScheduledThreadPoolExecutor .

  3. Servicio .

  4. BroadcastReciever con AlarmManager .

¿Cual es tu opinion?

EDITAR:
La razón por la que necesito esto es para una aplicación basada en chat que envía todas las acciones del usuario a un servidor remoto.
es decir, el usuario está escribiendo un mensaje, el usuario está leyendo un mensaje, el usuario está en línea, el usuario está fuera de línea, etc.

Esto significa que una vez en cada intervalo, necesito enviar al servidor lo que estoy haciendo, ya que abro una sala de chat con otras personas, necesitan saber lo que estoy haciendo.

Similar al mecanismo de retroalimentación de mensajes de WhatsApp: el mensaje parece entregado

EDICIÓN # 2: las
tareas recurrentes ahora deben programarse casi siempre a través de la JobSchedulerAPI (o FirebaseJobDispatcherpara API inferiores) para evitar problemas de agotamiento de la batería, como se puede leer en la sección de aspectos vitales de la capacitación de Android

EDICIÓN # 3:
FirebaseJobDispatcher ha quedado obsoleto y reemplazado por Workmanager , que también incorpora funciones de JobScheduler.

thepoosh
fuente
2
BroaccastReceiver con AlarmManager es bastante sencillo de usar. Es la única de las alternativas anteriores que he probado.
1
Hay pocas razones para usar un Timer sobre un ScheduledThreadPoolExecutor, que es más flexible ya que permite más de un subproceso en segundo plano y tiene una mejor resolución (solo útil para la resolución de ms) y permite el manejo de excepciones. En cuanto al AlarmManager, esta publicación brinda información sobre la diferencia.
Assylias
Para un ciclo de vida de ejecución corto, es decir, realizar alguna tarea cada 30 segundos en una actividad actualmente en primer plano, usar ScheduledThreadPoolExecutor (o Timer) es más eficiente. Para un ciclo de vida de larga duración, es decir, realizar alguna tarea cada 1 hora en un servicio en segundo plano, el uso de AlarmManager proporciona más confiabilidad.
yorkw
¿Por qué necesitas programar el envío? A partir de la descripción de su aplicación, ¿por qué no la envía en tiempo real?
iTech
porque el usuario asume que estás en línea, usando un tiempo de espera. es decir, si no he recibido un mensaje de "presencia" o "escribiendo" en el último X período de tiempo, automáticamente asumo que no lo estás haciendo
thepoosh

Respuestas:

164

No estoy seguro, pero según mi conocimiento, comparto mis puntos de vista. Siempre acepto la mejor respuesta si me equivoco.

Administrador de alarmas

El administrador de alarmas mantiene un bloqueo de activación de la CPU mientras se onReceive()esté ejecutando el método del receptor de alarma . Esto garantiza que el teléfono no se suspenderá hasta que haya terminado de manejar la transmisión. Una vez que onReceive()regresa, el administrador de alarmas libera este bloqueo de activación. Esto significa que, en algunos casos, el teléfono se dormirá tan pronto como onReceive()se complete el método. Si llamó su receptor de alarma Context.startService(), es posible que el teléfono se suspenda antes de que se inicie el servicio solicitado. Para evitar esto, su BroadcastReceivery Servicedeberá implementar una política de bloqueo de activación separada para garantizar que el teléfono continúe funcionando hasta que el servicio esté disponible.

Nota: El Administrador de alarmas está diseñado para casos en los que desea que el código de su aplicación se ejecute en un momento específico, incluso si su aplicación no se está ejecutando actualmente. Para las operaciones de cronometraje normales (ticks, tiempos de espera, etc.) es más fácil y mucho más eficiente utilizar Handler.

Temporizador

timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {

        synchronized public void run() {

            \\ here your todo;
            }

        }}, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(1));

Timertiene algunos inconvenientes que se resuelven mediante ScheduledThreadPoolExecutor. Entonces no es la mejor opción

ScheduledThreadPoolExecutor .

Puede usar java.util.Timero ScheduledThreadPoolExecutor(preferido) para programar una acción para que ocurra a intervalos regulares en un hilo en segundo plano.

Aquí hay una muestra usando este último:

ScheduledExecutorService scheduler =
    Executors.newSingleThreadScheduledExecutor();

scheduler.scheduleAtFixedRate
      (new Runnable() {
         public void run() {
            // call service
         }
      }, 0, 10, TimeUnit.MINUTES);

Así que preferí ScheduledExecutorService

Pero también piense en que si las actualizaciones ocurrirán mientras su aplicación se está ejecutando, puede usar un Timer, como se sugiere en otras respuestas, o el más reciente ScheduledThreadPoolExecutor. Si su aplicación se actualizará incluso cuando no se esté ejecutando, debe utilizar la extensión AlarmManager.

El Administrador de alarmas está diseñado para casos en los que desea que el código de su aplicación se ejecute en un momento específico, incluso si su aplicación no se está ejecutando actualmente.

Tenga en cuenta que si planea actualizar cuando su aplicación está apagada, una vez cada diez minutos es bastante frecuente y, por lo tanto, posiblemente consuma un poco de energía.

Md Maidul Islam
fuente
Estoy probando este método para una tarea periódica, pero no parece funcionar stackoverflow.com/questions/27872016/…
dowjones123
Para cosas simples, como verificar el estado cada n segundos, el temporizador funcionará.
IgorGanapolsky
1
@ Maid786 ¿Qué debemos usar si queremos hacer alguna tarea (como enviar Notificaciones) a intervalos de una semana o duración en días? ¿Alarm Manager tomará demasiados cálculos o procesamiento en segundo plano para eso?
Chintan Shah
30

Temporizador

Como se mencionó en los javadocs , es mejor usar un ScheduledThreadPoolExecutor.

ScheduledThreadPoolExecutor

Utilice esta clase cuando su caso de uso requiera varios subprocesos de trabajo y el intervalo de suspensión sea pequeño. Cuán pequeño ? Bueno, yo diría que unos 15 minutos. Los AlarmManagerintervalos de horario de inicio son en este momento y parece sugerir que para intervalos de sueño más pequeños se puede usar esta clase. No tengo datos para respaldar la última declaración. Es una corazonada.

Servicio

Su servicio puede ser cerrado en cualquier momento por la VM. No utilice los servicios para tareas recurrentes. Una tarea recurrente puede iniciar un servicio, que es otro asunto completamente diferente.

BroadcastReciever con AlarmManager

Para intervalos de sueño más largos (> 15 minutos), este es el camino a seguir. AlarmManagerya tiene constantes ( AlarmManager.INTERVAL_DAY) que sugieren que puede activar tareas varios días después de que se haya programado inicialmente. También puede activar la CPU para ejecutar su código.

Debe utilizar una de esas soluciones en función de sus necesidades de subproceso de trabajo y de tiempo.

Deepak Bala
fuente
1
Entonces, ¿qué pasaría si quisiera usar la aplicación y cada media hora quisiera hacer una copia de seguridad? Pero no quiero hacer una copia de seguridad mientras la aplicación no esté en uso (eso sería un desperdicio total). Alarmmanager repetirá continuamente la acción hasta que se reinicie (eso es al menos lo que he escuchado). ¿Qué recomendarías? ScheduledThreadPoolExecutor o Alarmmanager?
Hasdrúbal
13

Me doy cuenta de que esta es una pregunta antigua y ha sido respondida, pero esto podría ayudar a alguien. En tusactivity

private ScheduledExecutorService scheduleTaskExecutor;

En onCreate

  scheduleTaskExecutor = Executors.newScheduledThreadPool(5);

    //Schedule a task to run every 5 seconds (or however long you want)
    scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            // Do stuff here!

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // Do stuff to update UI here!
                    Toast.makeText(MainActivity.this, "Its been 5 seconds", Toast.LENGTH_SHORT).show();
                }
            });

        }
    }, 0, 5, TimeUnit.SECONDS); // or .MINUTES, .HOURS etc.
Emzor
fuente
2

Cotización de la programación de alarmas repetidas: comprender los documentos de compensaciones :

Un escenario común para activar una operación fuera de la vida útil de su aplicación es sincronizar datos con un servidor. Este es un caso en el que podría tener la tentación de utilizar una alarma repetitiva. Pero si es el propietario del servidor que aloja los datos de su aplicación, usar Google Cloud Messaging (GCM) junto con el adaptador de sincronización es una mejor solución que AlarmManager. Un adaptador de sincronización le brinda las mismas opciones de programación que AlarmManager, pero le ofrece una flexibilidad significativamente mayor.

Entonces, en base a esto, la mejor manera de programar una llamada al servidor es utilizando Google Cloud Messaging (GCM) junto con el adaptador de sincronización .

tato.rodrigo
fuente
1

He creado una tarea a tiempo en la que la tarea que el usuario desea repetir, agrega el método Run () de Custom TimeTask. se está repitiendo con éxito.

 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;
 import android.content.Intent;

 public class MainActivity extends Activity {

     CheckBox optSingleShot;
     Button btnStart, btnCancel;
     TextView textCounter;

     Timer timer;
     MyTimerTask myTimerTask;

     int tobeShown = 0  ;

@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);
    tobeShown = 1;

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

    btnStart.setOnClickListener(new OnClickListener(){

        @Override
        public void onClick(View arg0) {


            Intent i = new Intent(MainActivity.this, ActivityB.class);
            startActivity(i);

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

    btnCancel.setOnClickListener(new OnClickListener(){

        @Override
        public void onClick(View v) {
            if (timer!=null){
                timer.cancel();
                timer = null;
            }
        }
    });

}

@Override
protected void onResume() {
    super.onResume();

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


@Override
protected void onPause() {
    super.onPause();

    if (timer!=null){
        timer.cancel();
        timer = null;
    }

}

@Override
protected void onStop() {
    super.onStop();

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

}

hitesh141
fuente