• 20 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 12/12/2019

Améliorez votre AsyncTask

Vous devez maintenant être plutôt contents de vous, non ?

Parfait, car je vais vous poser une petite colle... :diable: : effectuez une rotation de votre écran pendant que votre AsyncTask est lancée.

Arf ! Il semblerait que celle-ci continue à s'exécuter dans un Thread à part mais la ProgressBar (celle en forme de cercle) ne s'affiche plus... En fait, durant la rotation de votre téléphone, l'activité a été détruite et avec elle, toutes ses vues dont la ProgressBar.

Le problème, c'est que l'utilisateur ne sait pas si la longue tâche précédemment lancée s'exécute toujours en arrière plan ou non ! Et ça, en tant que brillant étudiant d'OpenClassrooms et futur développeur de génie, vous ne pouvez pas laissez passer cela. JAMAIS !                                    

Nous allons donc découvrir un moyen de gérer correctement la coriace rotation d'Android, grâce à l'AsyncTaskLoader.

L'AsyncTaskLoader

Avant de parler de l'AsyncTaskLoader, il faut que je vous parle des Loaders. Ces derniers ont justement été créé afin de faciliter l'exécution et la gestion des tâches en arrière-plan, notamment lors des changements de configuration engendrés par Android (destruction d'une activité, rotation, etc...).

Autre subtilité des Loaders : La portée de leurs instances ne se limite pas uniquement à une activité ou un fragment, mais plutôt à l'application entière ! On peut donc les appeler dans une activité A et les récupérer dans une activité B ou encore un fragment C.

Dans notre cas, ils nous permettront de récupérer, après la rotation de l'application, une AsyncTask en cours d'exécution : Nous lancerons ainsi l'AsyncTask en mode portrait, et récupérerons cette même tâche, après la rotation, en mode paysage. Cool, non ?

En plus, afin de faciliter l'utilisation des loaders à travers une AsyncTask, les développeurs ont créé une classe qui s'appelle... AsyncTaskLoader. Qu'ils sont gentils ! C'est donc celle-ci que nous utiliserons dans ce chapitre.

Fonctionnement d'un Loader
Fonctionnement d'un Loader

Un loader possède trois méthodes principales, un peu sur le même modèle d'une AsyncTask d'ailleurs :

  • onCreateLoader():  Méthode appelée avant la création du loader et s'exécutant sur Main Thread. C'est d'ailleurs ici que nous démarrerons des animations de chargement (ProgressBar)

  • loadInBackground(): Méthode appelée dans un Thread dédié, et qui nous permettra comme vous l'aurez compris, d'exécuter des longues tâches sans gêner le bon fonctionnement de notre UI. 

  • onLoadFinished():  Méthode appelée lorsque le travail en arrière-plan s'est terminé, sur le Main Thread.

Puis, chaque Loader sera créé et géré par le LoaderManager, disponible dans chaque activité/fragment.

Passons maintenant à la pratique, et implémentons notre premier AsyncTaskLoader dans notre mini-application, Freezap (Vous pouvez retrouver également l'application à ce commit Github).

Pour cela, créez dans le package Utils la classe MyAsyncTaskLoader héritant de AsyncTaskLoader.

Classe Utils/MyAsyncTaskLoader.java :

public class MyAsyncTaskLoader extends AsyncTaskLoader<Long> {

    public MyAsyncTaskLoader(Context context) {
        super(context);
    }

    @Override
    public Long loadInBackground() {
        return Utils.executeLongActionDuring7seconds();
    }
    
    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        forceLoad();
    }
}

 Explications : Cette classe est très simple, puisqu'elle hérite de AsyncTaskLoader, et implémente :

  • loadInBackground(): On y place notre tâche longue afin de procéder à son exécution dans un Thread à part.

  • onStartLoading(): On la re-déclare afin de forcer le chargement des données, notamment après la réinitialisation du loader.

C'est tout. Le reste s'effectue dans notre activité ! Pour cela, modifions MainActivity.java.

Extrait de MainActivity.java :

public class MainActivity extends AppCompatActivity implements MyAsyncTask.Listeners, LoaderManager.LoaderCallbacks<Long> {
    
    // 1 - Implementation of loader callbacks
    
    // 3 - Create static task id that will identify our loader
    private static int TASK_ID = 100;

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

        ...

        //Try to resume possible loading AsyncTask
        this.resumeAsyncTaskLoaderIfPossible();
    }

    ...

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

    public void onClickButton(View v){
        ...
        switch (buttonTag){
            ...
            case 70: // CASE USER CLICKED ON BUTTON "EXECUTE ASYNCTASKLOADER"
                this.startAsyncTaskLoader();
                break;
        }
    }

    // -------------------------------------------
    // BACKGROUND TASK (HandlerThread & AsyncTask)
    // -------------------------------------------

    ...

    // 4 - Start a new AsyncTaskLoader
    private void startAsyncTaskLoader(){
        getSupportLoaderManager().restartLoader(TASK_ID, null, this);
    }

    // 7 - Resume previous AsyncTaskLoader if still running
    private void resumeAsyncTaskLoaderIfPossible(){
        if (getSupportLoaderManager().getLoader(TASK_ID) != null && getSupportLoaderManager().getLoader(TASK_ID).isStarted()) {
            getSupportLoaderManager().initLoader(TASK_ID, null, this);
            this.updateUIBeforeTask();
        }
    }

    // 2 - Implements callback methods
    
    @Override
    public Loader<Long> onCreateLoader(int id, Bundle args) {
        Log.e("TAG", "On Create !");
        this.updateUIBeforeTask();
        return new MyAsyncTaskLoader(this); // 5 - Return a new AsyncTaskLoader
    }

    @Override
    public void onLoadFinished(Loader<Long> loader, Long data) {
        Log.e("TAG", "On Finished !");
        loader.stopLoading(); // 6 - Force loader to stop
        updateUIAfterTask(data);
    }

    @Override
    public void onLoaderReset(Loader<Long> loader) { }
}

Explications : Dans notre activité, nous allons implémenter le callback qui nous permettra de gérer notre AsyncTaskLoader. Puis, grâce au LoaderManager, nous lancerons ou récupérerons l'exécution de notre longue tâche de 7 secondes.

  • Ligne 1 : Vous commencez à en avoir l'habitude, nous implémentons une interface (ici  LoaderManager.LoaderCallbacks) afin de pouvoir réaliser des actions supplémentaires avant/pendant/après l'exécution du Loader.

  • Ligne 2 : Notre interface implémentée, nous re-déclarons ses méthodes dans notre activité.

  • Ligne 3 : Chaque loader est identifié par un Integer, que nous avons créé ici.

  • Ligne 4 : Nous lançons (ou relançons au besoin), grâce à la méthode  restartLoader()  notre loader en lui spécifiant l'identifiant créé plus haut. N'oublions pas d'appeler cette méthode uniquement quand un utilisateur clique sur le bouton "Execute AsyncTaskLoader".

  • Ligne 5 : Automatiquement après le lancement, la méthode de callback onCreateLoader() est appelée, et nous lui retournons un loader, et plus précisément le loader MyAsyncTaskLoader. On en profite également pour mettre à jour notre UI en animant la ProgressBar.

  • Ligne 6 : Après que le traitement en arrière-plan est terminé, nous arrêtons le loader en question, ainsi que l'animation de la ProgressBar.

  • Ligne 7 : Cette ligne est plutôt magique, car elle va nous permettre de récupérer une tâche existante toujours en progression, en y souscrivant via nos méthodes de callback. Si jamais celle-ci existe, nous mettrons également à jour notre UI (animation de la ProgressBar). Cette méthode sera appelée dans le  onCreate()  de notre activité pour être lancée à chaque création (ou re-création) de celle-ci.

Lancez maintenant votre application, appuyez sur le bouton "Execute AsyncTaskLoader" et juste après, effectuez une rotation ! Votre application devrait reprendre l'exécution de la tâche longue et la ProgressBar continuer de s'afficher... ;) 

Puis une fois la tâche terminée, la ProgressBar devrait disparaitre.

                                                             

Exemple de certificat de réussite
Exemple de certificat de réussite