AsyncTask vs. RX: en un cas d’ús reduït

Fa poc estava treballant en una tasca on necessitava sincronitzar 12 peticions de xarxa de forma seqüencial. Sol·licita una aplicació JSON RESTful, una després de la següent.

Vaig estar treballant específicament per sol·licitar opcions des d’una càmera, que allotjava una API local amb la qual el vostre dispositiu Android es podia connectar per wifi. L’API retornaria quines opcions estaven disponibles i quins valors es podrien escollir per a cadascuna d’aquestes opcions.

Una API flexible, us permetia consultar la disponibilitat de diverses opcions alhora amb una sol·licitud. Tenia 12 opcions que m'interessaven, configuració i obertura d'exposició i opcions com aquesta.

L’únic problema era si una opció no estava disponible l’API de la càmera va tornar 404 com a resposta. I, fins i tot, quan vaig demanar múltiples! Per tant, si només faltava una opció de les 12, obtindríeu un 404 i no coneixeu res de les altres 11. Doncs això no serveix per res, vaig haver de passar per sol·licitar cada una de les opcions a la vegada.

Estava prenent cadascuna d’aquestes opcions i les vaig posar en un RecyclerView perquè l’usuari escollís la seva configuració mitjançant un filador.

Abans he utilitzat RX, concretament RXJava2, en aplicacions on he treballat. Però encara no he tingut l'oportunitat d'utilitzar-lo en el meu dia a dia en cooperar a la taula empresarial.

Incorporar les biblioteques a una base de codi empresarial que he trobat pot ser més difícil que les situacions d’inici o de llançament lliure. No és que les biblioteques no siguin grans o causin problemes. És que hi ha molta gent implicada en les decisions i cal ser bo en vendre diferents maneres de codificar.

Potser encara no sóc el millor a vendre idees, però estic intentant millorar!

Doncs aquí tinc l’exemple perfecte de la situació en la qual disposar de RX permetria fer aquestes 12 peticions més fàcils i sostenibles per a mi.

Normalment estàvem utilitzant AsyncTasks per al nostre treball de fons, com es fa en aquesta aplicació durant molt de temps. Legacy té tota la vida, un cop decidiu una tecnologia, seguirà aquesta aplicació durant un temps. Una altra raó per la qual aquestes decisions no es prenen a la lleugera.

A mi, acostumo a agradar provar coses noves i seguir avantguardistes.

Encara millor el que em va portar al punt on realment puc fer una comparació i un exemple de RX i AsyncTask, va ser el fet que una biblioteca de tercers que estàvem utilitzant depenia de la versió 1 de RXJava.

Tot baix i vet tot aquest temps, allà havia estat assegut a la nostra base de codis esperant ser utilitzat.

Per tant, jo i amb la meva aprovació dels companys de treball vam proposar fer un punt de referència per provar la diferència d'aquesta tasca entre l'ús de RX i AsyncTask.

Resulta que el temps és absolutament menyspreable! Tant de bo això elimini qualsevol mite que per a tasques de fons petites, l'ús d'AsyncTask sigui lent. M’ho expliquen força regularment de diverses fonts. Em pregunto què hi trobaria si fes proves més grans.

Vaig fer una petita mostra. Dirigir la meva activitat amb les dues solucions 6 vegades, i això és el que vaig obtenir:

RX:
11–17 08: 59: 00.086 12 RX Sol·licituds acabades per opcions: 3863ms
11–17 08: 59: 20.018 12 RX Sol·licituds acabades per opcions: 3816ms
11–17 08: 59: 39.143 12 RX Sol·licituds finalitzades per a opcions: 3628ms
11–17 08: 59: 57.367 12 RX Sol·licituds acabades per opcions: 3561ms
11–17 09: 00: 15.758 12 RX Sol·licituds acabades per opcions: 3713ms
11–17 09: 00: 39.129 12 RX Sol·licituds acabades d’opcions: 3612ms

Mitjana d'execució 3698,83ms per a la meva solució RX.

ATAsync:
11–17 08: 54: 49.277 12 Sol·licituds acabades per opcions: 4085ms
11–17 08: 55: 37.718 12 Sol·licituds acabades d’opcions: 3980ms
11–17 08: 55: 59.819 12 Sol·licituds acabades d’opcions: 3925ms
11–17 08: 56: 20.861 12 Sol·licituds acabades per opcions: 3736ms
11–17 08: 56: 41.438 12 Sol·licituds acabades d’opcions: 3549ms
11–17 08: 57: 01.110 12 Sol·licituds acabades d’opcions: 3833ms

Mitjana d'execució 3851.33ms per a la meva solució AsyncTask.

Utilitzar RX, segons la meva opinió, és poc o gens diferent. El que realitza el temps d'execució és realment l'operació dins d'aquesta tasca de fons que intenteu calcular.

El que RX us proporciona, però, és la sostenibilitat. El vostre codi és molt més fàcil de mantenir-lo al dia, menys propens a errors. Podeu escriure lògicament la vostra solució en el mateix ordre seqüencial en què s'executa. Es tracta d'un gran avantatge per fer lògicament el codi quan salteu de fred.

Si bé encara està bé, només cal utilitzar AsyncTasks i tothom pot fer el que sol fer, introduir RX va més enllà de les tasques de fons. Obteniu un món de noves oportunitats i maneres potents de canalitzar el vostre flux de treball i les vostres operacions. Hi ha moltes coses que pots fer amb RX que no pots fer amb AysncTasks.

Només cal veure el treball addicional que he de fer perquè la meva versió AsyncTask funcioni. He obuscat el codi per no mostrar cap empresa sensible. Això és una mena del meu codi real.

Versió d'AsyncTask:

public class OptionsCameraRequester implementa IOptionRepository {
    ATAsyncTask currentTask;
    boolean isCanceled;
    connector final HttpConnector;
    private long startTime;
    public OptionsCameraRequester (cadena ipAddress) {
        this.connector = nou HttpConnector (ipAddress);
    }
    public void cancel () {
        isCanceled = true;
        if (currentTask! = null) {
            currentTask.cancel (true);
            currentTask = null;
        }
    }
    public void getOptions (devolució de trucada) {
        si (isCanceled) torna;
        startTime = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "S'han iniciat les sol·licituds per a opcions");
        Iterador  iterador =
            CameraOption.getAllPossibleOptions (). Iterator ();
        requestOption (iterador, devolució de trucada);
    }
    void requestOption (iterador final del iterador ),
                       devolució de devolució final) {
        if (! iterator.hasNext ()) {
            temps llarg final = System.currentTimeMillis ();
            Log.i (MyLog.TAG, "S'han acabat les sol·licituds per a opcions:" +
                    (System.currentTimeMillis () - startTime) +
                    "Senyora");
            retorn;
        }
        opció final de CameraOption = iterator.next ();
        AsyncTask final  opció =
                AsyncTask nou  () {
                    CameraOption doInBackground (V ..) {
                        Resultat JSONObject =
                            connector.getOption (opció.getName ());
                        if (resultat == null) {
                            retornar nul;
                        } més {
                            // Feu una mica de treball amb JSONObject
                        }
                        opció de retorn;
                    }
                    void onPostExecute (opció CameraOption) {
                        OptionsCameraRequester.this.currentTask =
                            nul;
                        if (opció! = null) {
                            callback.onOptionAvailable (opció);
                        }
                        if (! isCanceled) {
                            requestOption (iterador, devolució de trucades);
                        }
                    }
                };
        task.execute ();
        currentTask = tasca;
    }
}

Versió RX:

public class OptionsCameraRequester implementa IOptionRepository {
    connector final HttpConnector;
    Subscripció getOptionsSubscription;
    public OptionsCameraRequester (cadena ipAddress) {
        this.connector = nou HttpConnector (ipAddress);
    }
    public void cancel () {
        if (getOptionsSubscription! = null) {
            getOptionsSubscription.unsubscribe ();
            getOptionsSubscription = null;
        }
    }
    // Utilitzo la devolució de trucades per poder adherir-me al mateix sistema
    // interfície i mantenir el codi RX contingut per a això
    // classe.

    public void getOptions (devolució de devolució final) {
        temps llarg final = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "S'han iniciat les sol·licituds de RX per a opcions");
        getOptionsSubscription =
        Observable.from (CameraOption.getAllPossibleOptions ())
            // Sol·liciteu cada opció des de la càmera
            .map (nou Func1  () {
                    
                trucada pública de CameraOption (opció CameraOption) {
                    Objecte JSONObject =
                        connector.getOption (opció.getName ());
                    if (objecte == null) {
                        cameraOption.setAvailable (false);
                    } més {
                        // Feu feina amb l'opció JSONObject to init
                    }
                    retorn de la càmera d'Opció;
               }
            })
            // Filtra les opcions que no són compatibles
            .filter (nou Func1  () {
                    
                public boolean call (CameraOption cameraOption) {
                    retornar cameraOption.isAvailable ();
                }
            })
            // Declar que el treball de fils es realitza i es continua
            .observeOn (AndroidSchedulers.mainThread ())
            .subscribeOn (Schedulers.newThread ())
            // Desactiveu cada opció a mesura que estigui a punt
            .subscribe (nou subscriptor  () {
         
                public void onCompletat () {
                   getOptionsSubscription = null;
                   Log.i (MyLog.TAG, "S'han acabat les sol·licituds de RX:" +
                        (System.currentTimeMillis () - temps) + "ms");
                }
                public void onError (Throwable e) {
                   MyLog.eLogErrorAndReport (MyLog.TAG, e);
                   callback.onError ();
                }
                public void onNext (CameraOption cameraOption) {
                    callback.onOptionAvailable (cameraOption);
                }
           });
    }
}

Si bé el codi RX té una aparença més llarga, ja no hi ha cap confiança en la gestió d’un iterador. No hi ha cap ruptura d'una trucada de funció recursiva que està canviant fils. No hi ha cap valor booleà per rastrejar que les tasques s’anul·len. Tot està escrit en l’ordre en què s’executa.