Agendando Tarefas Recorrentes no Android

122

Estou projetando um aplicativo que tem uma tarefa recorrente de enviar presença para um servidor dedicado, desde que o aplicativo esteja em primeiro plano.

Nas minhas pesquisas na web, vi algumas abordagens diferentes e queria saber qual é a melhor maneira de fazer isso.

Qual é a melhor maneira de agendar uma chamada de servidor?

As opções que vi foram:

  1. Temporizador .

  2. ScheduledThreadPoolExecutor .

  3. Serviço .

  4. BroadcastReciever com AlarmManager .

Qual a sua opinião?

EDIT:
A razão pela qual eu preciso disso é para um aplicativo baseado em bate-papo que envia todas as ações do usuário para um servidor remoto.
ou seja, o usuário está digitando uma mensagem, o usuário está lendo uma mensagem, o usuário está online, o usuário está offline etc.

Isso significa que, a cada intervalo, preciso enviar ao servidor o que estou fazendo, já que abro uma sala de bate-papo com outras pessoas, elas precisam saber o que estou fazendo.

Semelhante ao mecanismo de feedback da mensagem do whatsapp: a mensagem parece entregue

EDIÇÃO 2:
Agora, as tarefas recorrentes devem ser agendadas quase sempre por meio da JobSchedulerAPI (ou FirebaseJobDispatcherAPIs mais baixas), a fim de evitar problemas de esgotamento da bateria, conforme pode ser lido na seção vitais do treinamento do Android

EDIT # 3: O
FirebaseJobDispatcher foi preterido e substituído pelo Workmanager , que também incorpora recursos do JobScheduler.

thepoosh
fonte
2
O BroaccastReceiver com o AlarmManager é bastante simples de usar. É a única das alternativas acima que eu tentei.
1
Há poucas razões para usar um Timer em um ScheduledThreadPoolExecutor, que é mais flexível, pois permite mais de um encadeamento em segundo plano e possui uma melhor resolução (útil apenas para resolução de ms) e permite o tratamento de exceções. Quanto ao AlarmManager, este post fornece algumas informações sobre a diferença.
Assilias
Para um ciclo de vida de execução curto, ou seja, executar algumas tarefas a cada 30 segundos em uma atividade atualmente em primeiro plano, use ScheduledThreadPoolExecutor (ou Timer) é mais eficiente. Para um ciclo de vida longo, ou seja, executar algumas tarefas a cada 1 hora em um serviço em segundo plano, use o AlarmManager para oferecer mais confiabilidade.
yorkw
Por que você ainda precisa agendar o envio? Na descrição do seu aplicativo, por que você simplesmente não envia em tempo real?
iTech
porque o usuário assume que você está online, usando um tempo limite. ou seja, se eu não tiver recebido uma mensagem de "presença" ou "digitação" no período X passado, automaticamente presumo que você não esteja fazendo isso
thepoosh

Respostas:

164

Não tenho certeza, mas de acordo com meu conhecimento, compartilho minhas opiniões. Eu sempre aceito a melhor resposta se estiver errado.

Gerenciador de Alarmes

O Gerenciador de alarmes mantém um bloqueio de ativação da CPU enquanto o onReceive()método do receptor de alarme estiver em execução. Isso garante que o telefone não durma até que você termine de lidar com a transmissão. Uma vez onReceive()retornado, o Gerenciador de alarmes libera esse bloqueio de ativação. Isso significa que, em alguns casos, o telefone irá dormir assim que o onReceive()método for concluído. Se o seu receptor de alarme Context.startService()ligar, é possível que o telefone entre em suspensão antes do lançamento do serviço solicitado. Para evitar isso, o seu BroadcastReceivere Serviceprecisará implementar uma política de bloqueio esteira separada para garantir que o telefone continua a funcionar até que o serviço se torna disponível.

Nota: O Gerenciador de alarmes destina-se aos casos em que você deseja que o código do seu aplicativo seja executado em um horário específico, mesmo que seu aplicativo não esteja sendo executado no momento. Para operações normais de temporização (ticks, timeouts, etc), é mais fácil e muito mais eficiente usar o Handler.

Cronômetro

timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {

        synchronized public void run() {

            \\ here your todo;
            }

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

Timertem algumas desvantagens que são resolvidas por ScheduledThreadPoolExecutor. Portanto, não é a melhor escolha

ScheduledThreadPoolExecutor .

Você pode usar java.util.Timerou ScheduledThreadPoolExecutor(preferencial) para agendar uma ação para ocorrer em intervalos regulares em um encadeamento em segundo plano.

Aqui está uma amostra usando o último:

ScheduledExecutorService scheduler =
    Executors.newSingleThreadScheduledExecutor();

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

Então eu preferi ScheduledExecutorService

Mas pense também que, se as atualizações ocorrerem enquanto o aplicativo estiver em execução, você poderá usar um Timer, como sugerido em outras respostas, ou mais recente ScheduledThreadPoolExecutor. Se o seu aplicativo for atualizado mesmo quando não estiver em execução, você deverá usar o AlarmManager.

O Gerenciador de alarmes destina-se aos casos em que você deseja que o código do seu aplicativo seja executado em um horário específico, mesmo se o aplicativo não estiver em execução no momento.

Observe que, se você planeja atualizar quando o aplicativo é desligado, uma vez a cada dez minutos é bastante frequente e, portanto, pode consumir muita energia.

Md Maidul Islam
fonte
Estou tentando esse método para uma tarefa periódica, mas ele não parece funcionar com stackoverflow.com/questions/27872016/…
dowjones123
Para coisas simples - como verificar o estado a cada n segundos - o Timer fará.
IgorGanapolsky
1
@ Maid786 O que devemos usar se queremos executar alguma tarefa (como enviar notificações) no intervalo de uma semana ou duração em dias? O Alarm Manager precisará de muito cálculo ou processamento em segundo plano para isso?
Chintan Shah
30

Cronômetro

Conforme mencionado nos javadocs, é melhor usar um ScheduledThreadPoolExecutor.

ScheduledThreadPoolExecutor

Use esta classe quando seu caso de uso exigir vários encadeamentos de trabalho e o intervalo de suspensão for pequeno. Quão pequeno ? Bem, eu diria cerca de 15 minutos. Os AlarmManagerhorários de início dos intervalos neste momento e parece sugerir que, para intervalos menores de sono, essa classe possa ser usada. Não tenho dados para apoiar a última declaração. É um palpite.

Serviço

Seu serviço pode ser fechado a qualquer momento pela VM. Não use serviços para tarefas recorrentes. Uma tarefa recorrente pode iniciar um serviço, o que é outra questão completamente.

BroadcastReciever com AlarmManager

Para intervalos de sono mais longos (> 15 minutos), este é o caminho a percorrer. AlarmManagerjá possui constantes ( AlarmManager.INTERVAL_DAY), sugerindo que ele pode disparar tarefas vários dias após o agendamento inicial. Também pode ativar a CPU para executar seu código.

Você deve usar uma dessas soluções com base em suas necessidades de tempo e segmento de trabalho.

Deepak Bala
fonte
1
E se eu quisesse usar o aplicativo e a cada meia hora eu desejasse fazer um backup. Mas não quero fazer backup enquanto o aplicativo não estiver em uso (isso seria um desperdício total). O Alarmmanager repetirá continuamente a ação até a reinicialização (pelo menos foi o que ouvi). O que você recomendaria? ScheduledThreadPoolExecutor ou Alarmmanager?
Asdrúbal
13

Sei que essa é uma pergunta antiga e foi respondida, mas isso pode ajudar alguém. Na tuaactivity

private ScheduledExecutorService scheduleTaskExecutor;

No 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
fonte
2

Citando os alarmes repetidos de agendamento - Compreenda os documentos de trade-offs :

Um cenário comum para acionar uma operação fora da vida útil do seu aplicativo é sincronizar dados com um servidor. Este é um caso em que você pode ser tentado a usar um alarme repetido. Mas se você possui o servidor que hospeda os dados do aplicativo, usar o Google Cloud Messaging (GCM) em conjunto com o adaptador de sincronização é uma solução melhor que o AlarmManager. Um adaptador de sincronização oferece as mesmas opções de agendamento que o AlarmManager, mas oferece significativamente mais flexibilidade.

Portanto, com base nisso, a melhor maneira de agendar uma chamada de servidor é usar o Google Cloud Messaging (GCM) em conjunto com o adaptador de sincronização .

tato.rodrigo
fonte
1

Eu criei uma tarefa pontual na qual a tarefa que o usuário deseja repetir, adicione o método run () personalizado do TimeTask. está ocorrendo com ê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
fonte