Pianificazione di attività ricorrenti in Android


122

Sto progettando un'app che ha il compito ricorrente di inviare la presenza a un server dedicato fintanto che l'app è in primo piano.

Nelle mie ricerche sul Web ho visto alcuni approcci diversi e volevo sapere qual è il modo migliore per farlo.

Qual è il modo migliore per pianificare una chiamata al server?

Le opzioni che ho visto erano:

  1. Timer .

  2. ScheduledThreadPoolExecutor .

  3. Servizio .

  4. BroadcastReciever con AlarmManager .

Qual'è la tua opinione?

EDIT:
Il motivo per cui ne ho bisogno è per un'app basata su chat che invia tutte le azioni dell'utente a un server remoto.
cioè l'utente sta scrivendo un messaggio, l'utente sta leggendo un messaggio, l'utente è online, l'utente è offline ecc.

Ciò significa che una volta ogni intervallo, devo inviare al server quello che sto facendo, poiché apro una chat room con altre persone, loro hanno bisogno di sapere cosa sto facendo.

Simile al meccanismo di feedback dei messaggi di whatsapp: il messaggio sembra consegnato

EDIT # 2: le
attività ricorrenti dovrebbero ora essere pianificate quasi sempre tramite l' JobSchedulerAPI (o FirebaseJobDispatcherper API inferiori) al fine di evitare problemi di esaurimento della batteria, come si può leggere nella sezione dei dati vitali della formazione Android

EDIT # 3:
FirebaseJobDispatcher è stato deprecato e sostituito da Workmanager , che incorpora anche le funzionalità di JobScheduler.


2
BroaccastReceiver con AlarmManager è abbastanza semplice da usare. È l'unica delle alternative sopra che ho provato.

1
Ci sono poche ragioni per utilizzare un timer su ScheduledThreadPoolExecutor, che è più flessibile in quanto consente più di un thread in background e ha una risoluzione migliore (utile solo per la risoluzione ms) e consente la gestione delle eccezioni. Per quanto riguarda AlarmManager, questo post fornisce alcune informazioni sulla differenza.
assylias

Per un ciclo di vita di breve durata, ovvero eseguire alcune attività ogni 30 secondi in un'attività attualmente in primo piano, utilizzare ScheduledThreadPoolExecutor (o Timer) è più efficiente. Per un ciclo di vita di lunga durata, ovvero eseguire alcune attività ogni 1 ora in un servizio in background, l'uso di AlarmManager offre maggiore affidabilità.
yorkw

Perché hai anche bisogno di programmare l'invio? Dalla descrizione della tua app, perché non la invii in tempo reale?
iTech

perché l'utente presume che tu sia online, utilizzando un timeout. ciò significa che se non ho ricevuto un messaggio di "presenza" o "digitazione" negli ultimi X periodi di tempo, presumo automaticamente che tu non lo stia facendo
thepoosh

Risposte:


164

Non ne sono sicuro, ma per quanto ne so condivido le mie opinioni. Accetto sempre la migliore risposta se sbaglio.

Gestore allarmi

L'Alarm Manager mantiene un wakelock della CPU fintanto che il onReceive()metodo del ricevitore dell'allarme è in esecuzione. Ciò garantisce che il telefono non dormirà finché non avrai finito di gestire la trasmissione. Una volta onReceive()restituito, il gestore degli allarmi rilascia questo wakelock. Ciò significa che in alcuni casi il telefono dormirà non appena il onReceive()metodo sarà completato. Se il ricevitore della sveglia ha chiamato Context.startService(), è possibile che il telefono dorma prima che venga avviato il servizio richiesto. Per evitare questo, il vostro BroadcastReceivere Servicesarà necessario attuare una politica di blocco scia separato per garantire che il telefono continua a funzionare fino a quando il servizio diventa disponibile.

Nota: Gestione allarmi è concepito per i casi in cui si desidera che il codice dell'applicazione venga eseguito in un momento specifico, anche se l'applicazione non è attualmente in esecuzione. Per le normali operazioni di temporizzazione (tick, timeout, ecc.) È più facile e molto più efficiente utilizzare Handler.

Timer

timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {

        synchronized public void run() {

            \\ here your todo;
            }

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

Timerpresenta alcuni inconvenienti che vengono risolti da ScheduledThreadPoolExecutor. Quindi non è la scelta migliore

ScheduledThreadPoolExecutor .

È possibile utilizzare java.util.Timero ScheduledThreadPoolExecutor(preferito) per pianificare un'azione da eseguire a intervalli regolari su un thread in background.

Ecco un esempio che utilizza quest'ultimo:

ScheduledExecutorService scheduler =
    Executors.newSingleThreadScheduledExecutor();

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

Quindi ho preferito ScheduledExecutorService

Ma pensa anche che se gli aggiornamenti si verificheranno mentre la tua applicazione è in esecuzione, puoi usare un Timer, come suggerito in altre risposte, o il più recente ScheduledThreadPoolExecutor. Se la tua applicazione si aggiornerà anche quando non è in esecuzione, dovresti andare con il AlarmManager.

L'Alarm Manager è inteso per i casi in cui si desidera che il codice dell'applicazione venga eseguito in un momento specifico, anche se l'applicazione non è attualmente in esecuzione.

Tieni presente che se prevedi di eseguire l'aggiornamento quando l'applicazione è spenta, una volta ogni dieci minuti è abbastanza frequente e quindi potrebbe consumare un po 'troppo.


Sto provando questo metodo fuori per un task periodico, ma non sembra al lavoro stackoverflow.com/questions/27872016/...
dowjones123

Per cose semplici, come il controllo dello stato ogni n secondi, il timer va bene.
IgorGanapolsky

1
@ Maid786 Cosa dovremmo usare se vogliamo eseguire alcune attività (come l'invio di notifiche) a intervalli di una settimana o della durata in giorni? Alarm Manager richiederà troppi calcoli o elaborazioni in background per questo?
Chintan Shah

30

Timer

Come accennato in javadoc, è meglio usare ScheduledThreadPoolExecutor.

ScheduledThreadPoolExecutor

Usa questa classe quando il tuo caso d'uso richiede più thread di lavoro e l'intervallo di sospensione è piccolo. Quanto piccolo? Beh, direi circa 15 minuti. Gli AlarmManagerinizi programmano gli intervalli in questo momento e sembra suggerire che per intervalli di sonno più piccoli questa classe possa essere utilizzata. Non ho dati per supportare l'ultima affermazione. È un'intuizione.

Servizio

Il tuo servizio può essere chiuso in qualsiasi momento dalla VM. Non utilizzare i servizi per attività ricorrenti. Un'attività ricorrente può avviare un servizio, il che è completamente diverso.

BroadcastReciever con AlarmManager

Per intervalli di sonno più lunghi (> 15 minuti), questa è la strada da percorrere. AlarmManagerha già costanti ( AlarmManager.INTERVAL_DAY) che suggeriscono che può attivare attività diversi giorni dopo che è stato inizialmente pianificato. Può anche riattivare la CPU per eseguire il codice.

È necessario utilizzare una di queste soluzioni in base ai tempi e alle esigenze del thread di lavoro.


1
E se volessi usare l'app e ogni mezz'ora vorrei fare un backup. Ma non voglio fare un backup mentre l'app non è in uso (sarebbe uno spreco totale). Alarmmanager ripeterà continuamente l'azione fino al riavvio (questo è almeno quello che ho sentito). Cosa raccomanderesti? ScheduledThreadPoolExecutor o Alarmmanager?
hasdrubal

13

Mi rendo conto che questa è una vecchia domanda e ha avuto risposta, ma questo potrebbe aiutare qualcuno. Nel tuoactivity

private ScheduledExecutorService scheduleTaskExecutor;

Nel 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.

2

Citando la pianificazione degli allarmi ripetuti - Comprendere i documenti dei compromessi :

Uno scenario comune per l'attivazione di un'operazione al di fuori della durata dell'app è la sincronizzazione dei dati con un server. Questo è un caso in cui potresti essere tentato di utilizzare un allarme ripetuto. Ma se possiedi il server che ospita i dati della tua app, l'utilizzo di Google Cloud Messaging (GCM) insieme all'adattatore di sincronizzazione è una soluzione migliore di AlarmManager. Un adattatore di sincronizzazione offre tutte le stesse opzioni di pianificazione di AlarmManager, ma offre una flessibilità notevolmente maggiore.

Quindi, in base a questo, il modo migliore per pianificare una chiamata al server è utilizzare Google Cloud Messaging (GCM) insieme all'adattatore di sincronizzazione .


1

Ho creato un'attività in tempo in cui l'attività che l'utente desidera ripetere, aggiungere nel metodo run () TimeTask personalizzato. si sta ripetendo con successo.

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

}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.