Planification de tâches récurrentes sur Android

122

Je conçois une application qui a pour tâche récurrente d'envoyer la présence à un serveur dédié tant que l'application est au premier plan.

Dans mes recherches sur le Web, j'ai vu quelques approches différentes et je voulais savoir quelle est la meilleure façon de procéder.

Quelle est la meilleure façon de planifier un appel serveur?

Les options que j'ai vues étaient:

  1. Minuterie .

  2. ScheduledThreadPoolExecutor .

  3. Service .

  4. BroadcastReciever avec AlarmManager .

Quelle est ton opinion?

EDIT:
La raison pour laquelle j'en ai besoin est pour une application basée sur le chat qui envoie toutes les actions de l'utilisateur à un serveur distant.
c'est-à-dire que l'utilisateur tape un message, l'utilisateur lit un message, l'utilisateur est en ligne, l'utilisateur est hors ligne, etc.

Cela signifie qu'une fois à chaque intervalle, je dois envoyer au serveur ce que je fais, puisque j'ouvre une salle de chat avec d'autres personnes, elles doivent savoir ce que je fais.

Similaire au mécanisme de retour de message WhatsApp: le message semble livré

EDIT # 2:
Les tâches récurrentes doivent maintenant être planifiées presque toujours via l' JobSchedulerAPI (ou FirebaseJobDispatcherpour les API inférieures) afin d'éviter les problèmes d'épuisement de la batterie, comme on peut le lire dans la section vitales de la formation Android

EDIT # 3:
FirebaseJobDispatcher a été abandonné et remplacé par Workmanager , qui intègre également les fonctionnalités de JobScheduler.

thepoosh
la source
2
BroaccastReceiver avec AlarmManager est assez simple à utiliser. C'est la seule des alternatives ci-dessus que j'ai essayée.
1
Il y a peu de raisons d'utiliser un Timer sur un ScheduledThreadPoolExecutor, qui est plus flexible car il autorise plus d'un thread d'arrière-plan et a une meilleure résolution (utile uniquement pour la résolution ms) et permet la gestion des exceptions. En ce qui concerne AlarmManager, cet article donne quelques informations sur la différence.
assylias
Pour un cycle de vie court, c'est-à-dire effectuer une tâche toutes les 30 secondes dans une activité actuellement au premier plan, utiliser ScheduledThreadPoolExecutor (ou Timer) est plus efficace. Pour un cycle de vie long, c'est-à-dire effectuer une tâche toutes les 1 heure dans un service en arrière-plan, utiliser AlarmManager donne plus de fiabilité.
yorkw
Pourquoi avez-vous même besoin de planifier l'envoi? À partir de la description de votre application, pourquoi ne l'envoyez-vous pas simplement en temps réel?
iTech
car l'utilisateur suppose que vous êtes en ligne, en utilisant un délai d'expiration. ce qui signifie que si je n'ai pas reçu de message de "présence" ou de "saisie" au cours des X dernières années, je suppose automatiquement que vous ne le faites pas
thepoosh

Réponses:

164

Je ne suis pas sûr, mais selon mes connaissances, je partage mon point de vue. J'accepte toujours la meilleure réponse si je me trompe.

Gestionnaire d'alarmes

Le gestionnaire d'alarme maintient un verrou de réveil du processeur tant que la onReceive()méthode du récepteur d'alarme est en cours d'exécution. Cela garantit que le téléphone ne se mettra pas en veille tant que vous n'aurez pas fini de gérer la diffusion. Une fois onReceive()revenu, le gestionnaire d'alarme libère ce verrou de réveil. Cela signifie que le téléphone se mettra en veille dans certains cas dès que votre onReceive()méthode sera terminée. Si votre récepteur d'alarme a appelé Context.startService(), il est possible que le téléphone se mette en veille avant le lancement du service demandé. Pour éviter cela, votre BroadcastReceiveret Servicedevra mettre en œuvre une politique de verrouillage de réveil séparé pour faire en sorte que le téléphone continue de fonctionner jusqu'à ce que le service soit disponible.

Remarque: Le gestionnaire d'alarmes est destiné aux cas où vous souhaitez que le code de votre application s'exécute à un moment précis, même si votre application n'est pas en cours d'exécution. Pour les opérations de synchronisation normales (ticks, timeouts, etc.), il est plus facile et beaucoup plus efficace d'utiliser Handler.

Minuteur

timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {

        synchronized public void run() {

            \\ here your todo;
            }

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

Timerprésente certains inconvénients qui sont résolus par ScheduledThreadPoolExecutor. Donc ce n'est pas le meilleur choix

ScheduledThreadPoolExecutor .

Vous pouvez utiliser java.util.Timerou ScheduledThreadPoolExecutor(de préférence) pour planifier une action à intervalles réguliers sur un thread d'arrière-plan.

Voici un exemple utilisant ce dernier:

ScheduledExecutorService scheduler =
    Executors.newSingleThreadScheduledExecutor();

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

Alors j'ai préféré ScheduledExecutorService

Mais pensez également que si les mises à jour se produisent pendant que votre application est en cours d'exécution, vous pouvez utiliser a Timer, comme suggéré dans d'autres réponses, ou le plus récent ScheduledThreadPoolExecutor. Si votre application se met à jour même lorsqu'elle n'est pas en cours d'exécution, vous devez utiliser le AlarmManager.

Le gestionnaire d'alarmes est destiné aux cas où vous souhaitez que le code de votre application s'exécute à un moment donné, même si votre application n'est pas en cours d'exécution.

Notez que si vous prévoyez de mettre à jour lorsque votre application est désactivée, une fois toutes les dix minutes est assez fréquente, et donc peut-être un peu trop consommatrice d'énergie.

Md Maidul Islam
la source
J'essaie cette méthode pour une tâche périodique, mais cela ne semble pas fonctionner stackoverflow.com/questions/27872016/…
dowjones123
Pour des choses simples - comme la vérification de l'état toutes les n secondes -Timer fera l'affaire.
IgorGanapolsky
1
@ Maid786 Que devrions-nous utiliser si nous voulons effectuer une tâche (comme l'envoi de notifications) à intervalle d'une semaine ou d'une durée en jours? Alarm Manager prendra-t-il trop de calculs ou de traitements en arrière-plan pour cela?
Chintan Shah
30

Minuteur

Comme mentionné sur les javadocs, il vaut mieux utiliser un ScheduledThreadPoolExecutor.

ScheduledThreadPoolExecutor

Utilisez cette classe lorsque votre cas d'utilisation nécessite plusieurs threads de travail et que l'intervalle de veille est petit. Comment petit? Eh bien, je dirais environ 15 minutes. Les AlarmManagerintervalles du programme de démarrage à ce moment-là semblent suggérer que pour des intervalles de sommeil plus petits, cette classe peut être utilisée. Je n'ai pas de données pour soutenir la dernière déclaration. C'est une intuition.

Un service

Votre service peut être fermé à tout moment par la VM. N'utilisez pas de services pour des tâches récurrentes. Une tâche récurrente peut démarrer un service, ce qui est une tout autre affaire.

BroadcastReciever avec AlarmManager

Pour des intervalles de sommeil plus longs (> 15 minutes), c'est la voie à suivre. AlarmManagera déjà des constantes ( AlarmManager.INTERVAL_DAY) suggérant qu'il peut déclencher des tâches plusieurs jours après sa planification initiale. Il peut également réveiller le processeur pour exécuter votre code.

Vous devez utiliser l'une de ces solutions en fonction de vos besoins en matière de synchronisation et de thread de travail.

Deepak Bala
la source
1
Alors que faire si je voulais utiliser l'application, et toutes les demi-heures, je voudrais faire une sauvegarde. Mais je ne veux pas faire une sauvegarde pendant que l'application n'est pas utilisée (ce serait un gaspillage total). Alarmmanager répétera continuellement l'action jusqu'au redémarrage (c'est du moins ce que j'ai entendu). Que recommanderais-tu? ScheduledThreadPoolExecutor ou Alarmmanager?
hasdrubal
13

Je me rends compte que c'est une vieille question à laquelle on a répondu, mais cela pourrait aider quelqu'un. Dans votreactivity

private ScheduledExecutorService scheduleTaskExecutor;

Dans 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
la source
2

Citations de la planification des alarmes répétées - Comprendre les documents sur les compromis :

Un scénario courant pour déclencher une opération en dehors de la durée de vie de votre application est la synchronisation des données avec un serveur. C'est un cas où vous pourriez être tenté d'utiliser une alarme à répétition. Mais si vous possédez le serveur qui héberge les données de votre application, l'utilisation de Google Cloud Messaging (GCM) en conjonction avec l'adaptateur de synchronisation est une meilleure solution que AlarmManager. Un adaptateur de synchronisation vous offre les mêmes options de planification que AlarmManager, mais il vous offre beaucoup plus de flexibilité.

Sur cette base, le meilleur moyen de planifier un appel au serveur consiste donc à utiliser Google Cloud Messaging (GCM) en conjonction avec un adaptateur de synchronisation .

tato.rodrigo
la source
1

J'ai créé une tâche ponctuelle dans laquelle la tâche que l'utilisateur souhaite répéter, ajoute la méthode run () de TimeTask personnalisée. il se reproduit avec succès.

 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
la source