• 20 hours
  • Hard

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 12/12/19

Planifiez des tâches asynchrones

Maintenant que nous savons correctement créer des tâches asynchrones, il serait peut-être intéressant d'en planifier quelques-unes dans notre application Android non ?

C'est justement ce que nous allons faire dans ce chapitre ! Tic tac, le temps nous est compté...

AlarmManager

Imaginez, vous êtes le développeur en charge d'une application de livraison de repas. A midi pile, vous devez vérifier la disponibilité de plusieurs repas sur les serveurs de votre entreprise !

Mais alors comment faire ?

Eh bien on a juste à faire une boucle "Tant que", qui se terminera tous les jours à midi non ? :D

Non. :waw: Absolument pas ! Les développeurs d'Android ont été très très malins (comme d'habitude vous me direz), et ont donc développé la classe AlarmManager, permettant d'exécuter des actions prédéfinies, en fonction d'une contrainte horaire, et cela même si votre application n'est pas lancée.

Dans notre cas, nous souhaiterions afficher un message Toast toutes les 15 minutes pour indiquer à nos utilisateurs que nous les aimons profondément (en pratique ne faites pas ça, vous allez passer pour une personne plutôt relou...).

Dans un premier temps, nous allons créer un BroadcastReceiver nous permettant d'écouter au niveau de notre application entière, quand un événement particulier se produit. Cet événement sera dans notre cas, une ou des alarmes lancées par notre AlarmManager.

Créons donc notre BroadcastReceiver, MyAlarmReceiver, dans le package Utils de notre application Freezap (vous pouvez retrouver cette mini-application en cours de développement à travers ce commit Github)

Classe Utils/MyAlarmReceiver.java :

public class MyAlarmReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "We love you so much guys...", Toast.LENGTH_SHORT).show();
    }
}

 Afin de définir cette classe comme BroadcastReceiver, nous allons devoir la définir dans le manifeste de notre application grâce à des balises  <receiver> , tout simplement.

Extrait du fichier AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.openclassrooms.freezap">

    <application
        ... >
        
        ...

        <receiver
            android:name=".Utils.MyAlarmReceiver"
            android:exported="true"/>
            
    </application>

</manifest>

Modifions maintenant notre activité pour lancer et arrêter notre "alarme" lorsque l'on clique sur les boutons "Start Alarm" et "Stop Alarm".

Extrait de MainActivity.java :

public class MainActivity extends AppCompatActivity implements MyAsyncTask.Listeners, LoaderManager.LoaderCallbacks<Long> {

    ...
    
    // 1 - Creating an intent to execute our broadcast
    private PendingIntent pendingIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ...

        //Configuring The AlarmManager
        this.configureAlarmManager();
    }

    // ------------
    // ACTIONS
    // ------------

    public void onClickButton(View v){
        ...
        switch (buttonTag){
            ...
            case 30: // CASE USER CLICKED ON BUTTON "START ALARM"
                this.startAlarm();
                break;
            case 40: // CASE USER CLICKED ON BUTTON "STOP ALARM"
                this.stopAlarm();
                break;
            ...
        }
    }

    // -----------------
    // CONFIGURATION
    // -----------------

    ...

    // 2 - Configuring the AlarmManager
    private void configureAlarmManager(){
        Intent alarmIntent = new Intent(MainActivity.this, MyAlarmReceiver.class);
        pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    ...

    // ---------------------------------------------
    // SCHEDULE TASK (AlarmManager & JobScheduler)
    // ---------------------------------------------

    // 3 - Start Alarm
    private void startAlarm() {
        AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        manager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,0, AlarmManager.INTERVAL_FIFTEEN_MINUTES, pendingIntent);
        Toast.makeText(this, "Alarm set !", Toast.LENGTH_SHORT).show();
    }

    // 4 - Stop Alarm
    private void stopAlarm() {
        AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        manager.cancel(pendingIntent);
        Toast.makeText(this, "Alarm Canceled !", Toast.LENGTH_SHORT).show();
    }

    ...
}

Explications : Nous avons configuré ici une "alarme" via l'AlarmManager dont le but sera d'afficher un Toast toutes les 15 minutes.

  • Ligne 1 : Nous déclarons un Intent de type PendingIntent, qui permettra à l'AlarmManager de communiquer avec notre application, en lui indiquant notamment que des alarmes sont lancées.

  • Ligne 2 : Nous configurons le BroadcastReceiver précédemment créé (MyAlarmReceiver) afin de le placer dans un Pending Intent. Ce dernier transférera les événements envoyés par l'AlarmManager au BroadcastReceiver, qui lui affichera un Toast. N'oublions pas d'appeler cette méthode dans le  onCreate() de notre activité.

  • Ligne 3 : Cette méthode sera appelée pour programmer l'alarme (setRepeating()). Cette alarme sera exécutée toutes les 15 minutes, alertant ainsi à la même fréquence, MyAlarmReceiver qui affichera notre Toast.  N'oublions pas également de l'ajouter à notre méthode  onClickButton()  quand l'utilisateur cliquera sur le bouton "Start Alarm".

  • Ligne 4 : Cette méthode sera appelée dès que l'utilisateur souhaitera arrêter l'alarme en cours, notamment grâce à la méthode cancel() qui retirera notre intent précédemment inscrit.

Lancez maintenant votre application et démarrez votre alarme en appuyant sur le bouton "Start Alarm". Attendez (si vous le pouvez) 15 minutes, et appréciez le résultat ! ;)

JobScheduler

On peut considérer JobScheduler comme une évolution à notre AlarmManager, en ajoutant une granularité supplémentaire à la planification de tâches sur Android. La seule limitation réside dans le fait que la classe JobScheduler est disponible uniquement à partir des versions d'Android 5.0 (API 21)... 

Vous allez pouvoir par exemple, exécuter une tâche en arrière plan toutes les heures SI le téléphone est en train de charger ET SI ce dernier dispose d'une connexion internet active.

Bon cet exemple est tout de même bien sympa, et si on l'implémentait ? :-°

Pour cela, nous allons créer une classe dans notre package Utils, qui va hériter de JobService. En effet, JobScheduler ne peut lancer que des objets de type JobService. Ce "service" exécutera notre longue tâche de 7 secondes, MyAsyncTask, précédemment créée.

Classe Utils/SyncJobService.java :

public class SyncJobService extends JobService implements MyAsyncTask.Listeners {

    // 1 - Declare an AsyncTask and the parameters of the job
    private MyAsyncTask jobTask;
    private JobParameters jobParameters;

    @Override
    public boolean onStartJob(final JobParameters jobParameters) {
        Log.e(this.getClass().getSimpleName(), "SyncJob is started.");
        // 2 - When job starts, we execute our AsyncTask
        this.jobParameters = jobParameters;
        this.jobTask = new MyAsyncTask(this);
        this.jobTask.execute();
        return true;
    }

    @Override
    public boolean onStopJob(final JobParameters params) {
        Log.e(this.getClass().getSimpleName(), "SyncJob is stopped !");
        // 4 - We cancel the AsyncTask when Job stopped, mainly to avoid Memory Leaks
        if (this.jobTask != null) this.jobTask.cancel(true);
        return false;
    }

    @Override
    public void onPreExecute() {  }

    @Override
    public void doInBackground() { }

    @Override
    public void onPostExecute(Long taskEnd) {
        // 3 - When background task ended, we set the job as terminated
        Log.e("TAG", "Task ended at : "+taskEnd);
        jobFinished(jobParameters, false);
    }
}

Explications : Dans notre classe SyncJobService héritant de JobService, nous configurons un service qui exécutera notre précédente AsyncTask de 7 secondes. Ce service dispose de deux méthodes principales :  onStartJob()  et  onStopJob()  .

  • Ligne 1 : Nous déclarons ici deux variables de classe, une pour accéder plus facilement à notre AsyncTask, et une autre qui nous permettra d'indiquer que notre service est terminé (via la méthode jobFinished()) à la fin de l'AsyncTask.

  • Ligne 2 : Au démarrage de notre service, nous allons pouvoir lancer l'exécution de notre tâche longue (MyAsyncTask).

  • Ligne 3 : Quand notre tâche longue se termine, nous indiquerons à notre service que ce dernier est terminé grâce sa méthode  jobFinished()

  • Ligne 4 : Et enfin, comme notre service s'arrête, nous forçons notre AsyncTask à se terminer pour éviter d'éventuelles fuites de mémoire (Memory Leaks) en appelant sa méthode  cancel().

On n'oublie pas de dire à Android que cette classe contient un service en l'indiquant dans le manifeste de notre application grâce à la balise  <service>

Extrait du fichier AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.openclassrooms.freezap">

    <application
       ... >
        
        ...

        <service
            android:name=".Utils.SyncJobService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="true" />

    </application>

</manifest>

Et pour terminer, nous modifions notre activité principale afin qu'elle puisse lancer ce service lorsque l'utilisateur clique sur le bouton "Execute Job Scheduler".

Extrait de MainActivity.java :

public class MainActivity extends AppCompatActivity implements MyAsyncTask.Listeners, LoaderManager.LoaderCallbacks<Long> {
    
    ...
    
    // 1 - Create an ID for JobScheduler
    private static int JOBSCHEDULER_ID = 200;
    
    ...

    // ------------
    // ACTIONS
    // ------------

    public void onClickButton(View v){
        ...
        switch (buttonTag){
            ...
            case 50: // CASE USER CLICKED ON BUTTON "EXECUTE JOB SCHEDULER"
                this.startJobScheduler();
                break;
            ...
        }
    }
    
    ...
    
    // ---------------------------------------------
    // SCHEDULE TASK (AlarmManager & JobScheduler)
    // ---------------------------------------------

    ...

    // 2 - Start service (job) from the JobScheduler
    private void startJobScheduler(){
        // 2.1 Create a builder that will build an object JobInfo containing launching conditions and the service
        JobInfo job = new JobInfo.Builder(JOBSCHEDULER_ID, new ComponentName(this, SyncJobService.class))
                .setRequiresCharging(true) // The job will be executed if charging
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) // The job will be executed if any network is available
                .setPeriodic(3600000) // The job will be executed each hour
                .build();

        // 2.2 - Get the JobScheduler and schedule the previous job
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        jobScheduler.schedule(job);
    }

    // 3 - Stop service (job) from the JobScheduler
    private void stopJobScheduler(){
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        jobScheduler.cancel(JOBSCHEDULER_ID);
    }

   ...
}

Explications : Quand l'utilisateur appuiera sur le bouton "Execute Job Scheduler", le JobScheduler programmera l'exécution d'un service (en l'occurrence SyncJobService) toutes les heures, si deux conditions sont réunies (Le téléphone est en train de charger et le téléphone dispose d'une connexion internet) :

  • Ligne 1 : Nous créons un identifiant nous permettant d'identifier le service que nous lancerons par la suite.

  • Ligne 2 : Nous créons la méthode  startJobScheduler  (et n'oublions pas de la définir dans la méthode  onClickButton  ) qui programmera l'exécution de notre service.

    • Ligne 2.1 : Nous créons un Builder de type JobInfo, qui nous permettra de définir le service en question à lancer, ses caractéristiques temporelles (ici toutes les heures) et ses conditions de lancement (ici SI le téléphone est en train de charger et SI le téléphone dispose d'une connexion internet).

    • Ligne 2.2 : On programme notre service grâce à l'objet JobInfo précédemment créé.

  • Ligne 3 : Nous créons une méthode qui nous permettra de déprogrammer un service au besoin (nous ne l'utiliserons pas dans l'application) en utilisant la méthode  cancel()  du JobScheduler.

N'hésitez pas à tester ce code avec des valeurs de temps un peu plus basses afin de vous rendre compte plus rapidement du résultat !

Pour terminer, je vous conseille de jeter un coup d'oeil à la librairie Android-Job créée par Evernote, basée sur AlarmManager et JobScheduler, et qui vous permettra de planifier BEAUCOUP plus facilement des tâches en arrière-plan.

Hein mais quoi ? On a appris tout cela pour rien ?

Même si je vous conseille personnellement cette libraire comme alternative à AlarmManager et JobScheduler, il est important de tout même de les connaitre, afin de comprendre en détails ce qui se cache derrière ces concepts car il est très probable que vous les rencontreriez un jour. Et puis sait-on jamais, cette librairie (Android-Job) peut disparaitre dans 5 ans, ou peut-être que vous serez amenés à maintenir du code un peu vieux, etc... ;)

Example of certificate of achievement
Example of certificate of achievement