AsyncTask

Gestion des opérations asynchrones avec AsyncTask

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com

Certaines opérations sous Android peuvent être gourmandes en ressources. Le chargement de gros fichiers, le traitement des données lourdes peuvent décourager l'utilisateur s'ils sont exécutés dans la partie visible, c'est-à-dire dans l'interface utilisateur. Pour éviter cette situation, Android rend possible le chargement de ces éléments via les tâches asynchrones.

Pourquoi utiliser les tâches asynchrones sous Android ?

Android possède un thread principal, appelé aussi main thread. Si l'utilisateur ne définit pas d'autres stratégie de gestion d'accès concurrentiel à des ressources, c'est lui qui exécute toutes les opérations.

Quand on y exécute une opération nécessitant un long temps de traitement, l'interface utilisateur peut se bloquer. Android peut alors afficher un message Application not responding (ANR). Il apparaît au bout d'un certain temps critique, selon lequel le système considère une action comme bloquée.

Pour éviter ce problème dans le cas des traitements complexes, Anroid nous invite à utiliser les tâches asynchrones.

Tâches asynchrones sous Android

Les tâches asynchrones sous Android peuvent être lancées grâce à la classe AsyncTask. Il regroupe deux concepts importants du système pour pouvoir exécuter de longues actions sans gêner le thread principal. Ces deux concepts sont :
- thread : pour exécuter une longue opération, on peut la placer dans un nouveau thread qui sera synchronisé avec main thread. Si l'on a besoin de faire tourner des threads pendant un grand laps du temps, on devrait se baser sur les bienfaits des composants du package java.util.concurrent (Executor, ThreadPoolExecutor et FutureTask).
- Handler : cette classe nous autorise à envoyer et à traiter les instances de la classe Message ou de l'interface Runnable. Deux usages de l'Handler sont connus : quand on veut exécuter des Message ou Runnable dans un point précis dans le futur ou quand on veut exécuter une action dans un autre thread que le nôtre.

AsyncTask regroupe ces deux notions. Il lance une nouvelle tâche dans un thread de background qui peut interagir avec le thread principal. Cependant, AsyncTask ne devrait être utilisé que pour les tâches qui nécessitent un temps de traitement de quelques secondes. Une instance ne peut pas être lancée qu'une seule fois. Dans le cas d'exécutions multiples, une exception est lancée.

L'exécution des AsyncTask doit se faire depuis le main thread (celui qui gère l'interface utilisateur). Depuis la 3e version de l'Android, des tâches asynchrones sont exécutées dans un seul thread commun.

Implémenter AsyncTask sous Android

AsyncTask est composé de 4 étapes :

  1. Avant l'exécution : dans la méthode onPreExecute() on peut placer les actions qui doivent se faire avant l'exécution réelle de la tâche.
  2. Exécution : invoquée directement après onPreExecute. Il s'agit de la méthode doInBackground() qui s'occupe d'exécuter réellement une tâche.
  3. Pendant l'exécution : action gérée dans la fonction onProgressUpdate(), elle permet d'indiquer à l'utilisateur le progrès de traitement des données à l'étape 3.
  4. Après l'exécution : la méthode onPostExecute() utilisée à cette étape, s'occupe de gérer le résultat de traitement, transmis dans ses paramètres.

Regardons un exemple issu de notre application qui implémente cette logique. On illustrera l'implémentation de l'AsyncTask sur la base du chargement d'un livre dans l'activité OneBookActivity. Cette activité définira une Map des paramètres transmis à la tâche. Puis elle exécutera la tâche avec la méthode execute(). Le résultat du chargement sera géré dans la méthode fillUpBookData(). Elle s'occupera de remplir le layout avec des données sur le livre. Voici le code de cette activité :

public class OneBookActivity extends BaseActivity  {
    // ...
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        Map<String, Object> params = new HashMap();
        params.put("activity", this);
        params.put("id", bookId);
        BookGetterAsynTask bookGetterAsynTask = new BookGetterAsynTask();
        bookGetterAsynTask.execute(params);
    }
    
    public void fillUpBookData(Map<String, Object> result) {
        progressDialog.dismiss();
        if(!handleResponseType((Map<String, String>) result.get(ClientREST.KEY_STATE))) {
            Log.d(LOG_TAG, "Found book details");
            Book book = (Book) result.get("book");
            BookHelperView bookHelperView = new BookHelperView();    
            bookHelperView.setBook(book);
            bookHelperView.setActivity(this);
            List<Customization> customizations = customizationDataSource.getAllCustomizations();
            Log.d(LOG_TAG, "Found customizations list " + customizations);
            if(customizations != null && customizations.size() > 0) {
                for(Customization customization : customizations) {
                    Log.d(LOG_TAG, "Customizing for " +customization);
                    bookHelperView.constructFromId(getResources().getIdentifier(""+customization.getId(), "id", "com.example.library"), customization.getMethod());
                }
            }
        }
    }

}

Le code de la tâche asynchrone est court. Tout le travail est fait par la classe qui s'occupe d'aller chercher les informations dans le web service (BookClientREST). Voici le code de cette classe :

public class BookGetterAsynTask extends
AsyncTask<Map<String, Object>, Integer, Map<String, Object>> {

    private final static String LOG_TAG = "BookGetterAsynTask";
    private OneBookActivity activity; 

    @Override
    protected Map<String, Object> doInBackground(Map<String, Object>... data) {
        try {
            activity = (OneBookActivity) data[0].get("activity");
            BookClientREST bookClientREST = new BookClientREST();
            bookClientREST.setActivity(activity);
            return bookClientREST.getBook((Long) data[0].get("id"));
        } catch(Exception e) {
            Log.e(LOG_TAG, " An erro occ", e);
        }
        return null;
    }

    @Override
    protected void onPostExecute(Map<String, Object> result) { 
        activity.fillUpBookData(result);
    }

}

La chose qui peut surprendre sont des types génériques dans AsyncTask<Map<String, Object>, Integer, Map<String, Object>>, dont les parties :
- <Map<String, Object> : correspond aux paramètres envoyés (params de la méthode onCreate())
- Integer : correspond à l'indication du progrès de traitement
- Map<String, Object> : correspond au résultat attendu (transmis à la méthode fillUpBookData())

Bartosz KONIECZNY Tâches asynchrones

Une question ? Une remarque ?

*

*

Un conseil MySQL

Vous rencontrez un problème lors de la suppression d'un élément ou d'une table liée à des contraintes des clés étrangères. L'une des solution peut être l'annulation de la vérification de ces contraintes.


-- Au début on indique à MySQL de ne pas vérifier les contraintes des clés étrangères
SET FOREIGN_KEY_CHECKS=0;
-- Ensuite on passe à l'opération de suppression d'une table
DROP TABLE ma_table;
-- A la fin (ou au moment voulu) on restaure la vérification des contraintes
SET FOREIGN_KEY_CHECKS=1;

Vous pouvez en savoir plus sur FOREIGN_KEY_CHEKS dans la documentation du MySQL.