AsyncTask vs RX - Dans un petit cas d'utilisation

Récemment, je travaillais sur une tâche où je devais synchroniser 12 demandes réseau de manière séquentielle. Demandes api JSON RESTful, l'une après l'autre.

Je travaillais spécifiquement sur la demande d'options à partir d'une caméra, qui hébergeait une API locale avec laquelle votre appareil Android pouvait se connecter via wifi. L'API renverrait les options disponibles et les valeurs pouvant être choisies pour chacune de ces options.

Une API flexible, elle vous a permis d'interroger la disponibilité de plusieurs options en même temps avec une seule demande. J'avais 12 options qui m'intéressaient, les paramètres d'exposition et l'ouverture, et des options comme ça.

Le seul problème était que si aucune option n'était disponible, l'API de l'appareil photo renvoyait 404 comme réponse. Et, même lorsque j'en ai demandé plusieurs! Donc, si une seule option sur 12 manquait, vous obtiendriez une 404 et vous ne savez rien des 11 autres. Eh bien, cela ne sert à rien, je devais passer à la demande de chaque option, une à la fois.

Je prenais chacune de ces options et les mettais dans un RecyclerView pour que l'utilisateur puisse choisir ses paramètres via un spinner.

J'ai déjà utilisé RX, en particulier RXJava2, dans les applications sur lesquelles j'ai travaillé. Mais je n'avais pas encore eu l'occasion de l'utiliser dans mon travail quotidien de bureau d'entreprise.

L'intégration de bibliothèques dans une base de code d'entreprise que j'ai trouvée peut être plus difficile que les situations de démarrage ou indépendantes. Ce n'est pas que les bibliothèques ne sont pas géniales ou causent des problèmes. C'est qu'il y a beaucoup de gens impliqués dans les décisions et vous devez être bon pour vendre différentes façons de coder.

Je ne suis peut-être pas encore le meilleur en vente d'idées, mais j'essaie de m'améliorer!

Eh bien ici, j'ai l'exemple parfait de la situation où avoir RX rendrait ces 12 demandes plus faciles et plus faciles à gérer pour moi.

Nous utilisions généralement AsyncTasks pour notre travail de fond, comme cela a été fait dans cette application depuis longtemps. L'héritage a une durée de vie, une fois que vous aurez choisi une technologie, il suivra cette application pendant un certain temps. Une autre raison pour laquelle ces décisions ne sont pas prises à la légère.

Moi, j'ai tendance à aimer essayer de nouvelles choses et à rester à la pointe.

Encore mieux, ce qui m'a amené au point où je peux réellement faire une comparaison et un exemple de RX et AsyncTask, c'est le fait qu'une bibliothèque tierce que nous utilisions dépendait de RXJava version 1.

Faible et voici tout ce temps, il était assis dans notre base de code en attente d'être utilisé.

Donc, avec l'approbation de mes collègues, j'ai décidé de faire un test pour tester la différence pour cette tâche entre l'utilisation de RX et AsyncTask.

Il s'avère que le timing est absolument négligeable! Espérons que cela dissipe tous les mythes selon lesquels pour les petites tâches d'arrière-plan, l'utilisation d'AsyncTask est lente. On me le dit assez régulièrement à partir de plusieurs sources. Je me demande ce que je trouverais si je faisais de plus gros tests.

J'ai fait un petit set d'échantillons. Exécuter mon activité avec les deux solutions 6 fois et voici ce que j'ai obtenu:

RX:
11-17 08: 59: 00.086 12 RX Demandes terminées pour les options: 3863 ms
11-17 08: 59: 20.018 12 RX Demandes terminées pour les options: 3816 ms
11-17 08: 59: 39.143 12 RX Demandes terminées pour les options: 3628ms
11-17 08: 59: 57.367 12 Demandes RX terminées pour les options: 3561ms
11-17 09: 00: 15.758 12 RX Demandes terminées pour les options: 3713ms
11-17 09: 00: 39.129 12 RX Demandes terminées pour les options: 3612ms

Durée d'exécution moyenne de 3698,83 ms pour ma solution RX.

ATAsync:
11-17 08: 54: 49.277 12 Demandes d'options terminées: 4085 ms
11-17 08: 55: 37.718 12 Demandes d'options terminées: 3980ms
11-17 08: 55: 59.819 12 Demandes d'options terminées: 3925 ms
11-17 08: 56: 20.861 12 Demandes d'options terminées: 3736ms
11-17 08: 56: 41.438 12 Demandes d'options terminées: 3549ms
11-17 08: 57: 01.110 12 Demandes d'options terminées: 3833 ms

Durée d'exécution moyenne 3851,33 ms pour ma solution AsyncTask.

À mon avis, l'utilisation de RX fait peu ou pas de différence dans les temps d'exécution. Vraiment, ce qui compose le runtime est l'opération à l'intérieur de cette tâche en arrière-plan que vous essayez de calculer.

Ce que RX vous donne cependant, c'est la maintenabilité. Votre code est beaucoup plus facile à maintenir à jour, moins sujet aux erreurs. Vous pouvez logiquement écrire votre solution dans le même ordre séquentiel dans lequel elle est exécutée. C'est un énorme bonus pour grogner logiquement le code, lorsque vous sautez dans le froid.

Bien qu'il soit toujours correct d'utiliser simplement AsyncTasks et que tout le monde puisse faire ce qu'il fait habituellement, l'introduction de RX va au-delà des tâches d'arrière-plan. Vous obtenez un monde de nouvelles opportunités et de moyens puissants pour canaliser fonctionnellement votre flux de travail et vos opérations. Il y a beaucoup de choses que vous pouvez faire avec RX que vous ne pouvez pas faire avec AysncTasks.

Il suffit de regarder le travail supplémentaire que je dois faire pour que ma version AsyncTask fonctionne. J'ai masqué le code afin de ne rien montrer de sensible à l'entreprise. Ceci est une maquette de mon code actuel.

Version AsyncTask:

Classe public class OptionsCameraRequester implémente IOptionRepository {
    ATAsyncTask currentTask;
    boolean isCanceled;
    connecteur HttpConnector final;
    privé long startTime;
    public OptionsCameraRequester (String ipAddress) {
        this.connector = new HttpConnector (ipAddress);
    }
    public void cancel () {
        isCanceled = true;
        if (currentTask! = null) {
            currentTask.cancel (true);
            currentTask = null;
        }
    }
    public void getOptions (rappel de rappel) {
        if (isCanceled) return;
        startTime = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "Demandes démarrées pour les options");
        Itérateur  itérateur =
            CameraOption.getAllPossibleOptions (). Iterator ();
        requestOption (itérateur, rappel);
    }
    void requestOption (final Iterator  itérateur,
                       rappel de rappel final) {
        if (! iterator.hasNext ()) {
            temps long final = System.currentTimeMillis ();
            Log.i (MyLog.TAG, "Demandes terminées pour les options:" +
                    (System.currentTimeMillis () - startTime) +
                    "Mme");
            revenir;
        }
        option CameraOption finale = iterator.next ();
        final AsyncTask  task =
                new AsyncTask  () {
                    CameraOption doInBackground (V ..) {
                        Résultat JSONObject =
                            connector.getOption (option.getName ());
                        if (résultat == null) {
                            return null;
                        } autre {
                            // Travaillez avec JSONObject
                        }
                        option de retour;
                    }
                    void onPostExecute (option CameraOption) {
                        OptionsCameraRequester.this.currentTask =
                            nul;
                        if (option! = null) {
                            callback.onOptionAvailable (option);
                        }
                        if (! isCanceled) {
                            requestOption (itérateur, rappel);
                        }
                    }
                };
        task.execute ();
        currentTask = tâche;
    }
}

Version RX:

Classe public class OptionsCameraRequester implémente IOptionRepository {
    connecteur HttpConnector final;
    Abonnement getOptionsSubscription;
    public OptionsCameraRequester (String ipAddress) {
        this.connector = new HttpConnector (ipAddress);
    }
    public void cancel () {
        if (getOptionsSubscription! = null) {
            getOptionsSubscription.unsubscribe ();
            getOptionsSubscription = null;
        }
    }
    // J'utilise le rappel pour pouvoir adhérer au même système
    // interface et garder le code RX contenu juste pour cela
    // classe.

    public void getOptions (rappel de rappel final) {
        temps long final = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "Demandes RX démarrées pour les options");
        getOptionsSubscription =
        Observable.from (CameraOption.getAllPossibleOptions ())
            // Demande chaque option à la caméra
            .map (nouveau Func1  () {
                    
                appel public CameraOption (option CameraOption) {
                    Objet JSONObject =
                        connector.getOption (option.getName ());
                    if (objet == null) {
                        cameraOption.setAvailable (false);
                    } autre {
                        // Travaillez avec JSONObject vers l'option init
                    }
                    return cameraOption;
               }
            })
            // Filtrer les options non prises en charge
            .filter (nouveau Func1  () {
                    
                appel booléen public (CameraOption cameraOption) {
                    return cameraOption.isAvailable ();
                }
            })
            // Le travail de déclaration des threads est terminé et reçu le
            .observeOn (AndroidSchedulers.mainThread ())
            .subscribeOn (Schedulers.newThread ())
            // Faites passer chaque option lorsqu'elle est prête
            .subscribe (nouvel abonné  () {
         
                public void onCompleted () {
                   getOptionsSubscription = null;
                   Log.i (MyLog.TAG, "Demandes RX terminées:" +
                        (System.currentTimeMillis () - heure) + "ms");
                }
                public void onError (Throwable e) {
                   MyLog.eLogErrorAndReport (MyLog.TAG, e);
                   callback.onError ();
                }
                public void onNext (CameraOption cameraOption) {
                    callback.onOptionAvailable (cameraOption);
                }
           });
    }
}

Bien que le code RX semble plus long, il n'est plus nécessaire de gérer un itérateur. Il n'y a pas de rupture d'un appel de fonction récursive qui change de threads. Il n'y a pas de valeur booléenne pour suivre que les tâches sont annulées. Tout est écrit dans l'ordre d'exécution.