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 ?

*

*

Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil PHP

Utiliser XPath pour calculer le nombre d'apparitions d'un élément.

XPath est un langage qui sert à se déplacer au sein d'un document XML. Il contient beaucoup de fonctionnaltiés utilies lors de l'analyse de ces documents. Par exemple, pour voir le nombre d'apparition d'un tag, il suffit d'utiliser la méthode count et placer comme son paramètre le chemin vers les éléments à calculer. Imaginons qu'on veut calculer le nombre de noeds d'erreur. En PHP cela va se présenter ainsi :

// $this->xpath is the instance of DOMXPath
echo (int)$this->xpath->evaluate("count(/error)");