AsyncTask vs. RX - I et lille tilfælde

For nylig arbejdede jeg på en opgave, hvor jeg havde brug for at synkronisere 12 netværksanmodninger i rækkefølge. RESTful JSON api anmodninger, den ene efter den næste.

Jeg arbejdede specifikt på at anmode om indstillinger fra et kamera, som var vært for en lokal API, som din Android-enhed kunne oprette forbindelse til via wifi. API'en returnerer, hvilke muligheder der var tilgængelige, og hvilke værdier der kunne vælges for hver af disse indstillinger.

En fleksibel API, det gjorde det muligt for dig at spørge om tilgængeligheden af ​​flere indstillinger på samme tid med en anmodning. Jeg havde 12 indstillinger, jeg var interesseret i, eksponeringsindstillinger og blændeåbning, og muligheder som det.

Det eneste problem var, hvis en mulighed ikke var tilgængelig, returnerede kameraets API 404 som svar. Og selv når jeg anmodede om flere! Så hvis der kun manglede en mulighed ud af de 12, ville du få en 404 og ikke vide noget om den anden 11. Det er nytteløst, jeg var nødt til at skifte til at anmode om hver mulighed, en ad gangen.

Jeg tog hver af disse indstillinger og satte dem i en RecyclerView for brugeren at vælge deres indstillinger via en spinner.

Jeg har brugt RX før, specifikt RXJava2, i apps, jeg har arbejdet på. Men jeg havde ikke haft muligheden for at bruge det i mit daglige samarbejdsvillige enterprise desk job.

At bringe biblioteker ind i en firmakodebase, som jeg har fundet, kan være mere udfordrende end opstart eller frilans-situationer. Det er ikke, at bibliotekerne ikke er store eller skaber problemer. Det er, at der er mange mennesker, der er involveret i beslutningerne, og du skal være god til at sælge forskellige måder at kode på.

Jeg er måske ikke den bedste til at sælge ideer endnu, men jeg prøver at forbedre mig!

Nå her har jeg det perfekte eksempel på en situation, hvor det at gøre RX ville gøre det lettere og mere vedligeholdeligt at gøre disse 12 anmodninger for mig.

Vi brugte typisk AsyncTasks til vores baggrundsarbejde, som det er blevet gjort i denne app i lang tid. Legacy har en levetid, når du først har besluttet dig for en teknologi, vil den følge den app i et stykke tid. En anden grund til, at disse beslutninger ikke træffes let.

Mig, jeg har en tendens til at kunne lide at prøve nye ting og forblive i forkant.

Endnu bedre, hvad der fik mig til det punkt, hvor jeg faktisk kan foretage en sammenligning og et eksempel på RX og AsyncTask, var det faktum, at et tredjepartsbibliotek, som vi brugte, var afhængigt af RXJava version 1.

Lav og se hele denne tid, der havde den siddet i vores kodebase og ventet på at blive brugt.

Så jeg og med min medarbejdergodkendelse besluttede at gøre benchmark for at teste forskellen for denne ene opgave mellem at bruge RX og AsyncTask.

Det viser sig, at timingen er absolut ubetydelig! Forhåbentlig udelukker dette myter om, at der med små baggrundsopgaver er at bruge AsyncTask langsomt. Jeg får at vide det ganske regelmæssigt fra flere kilder. Jeg spekulerer på, hvad jeg ville finde, hvis jeg lavede større prøver.

Jeg lavede et lille prøvesæt. Kører min aktivitet med begge løsninger 6 gange, og det er, hvad jeg fik:

RX:
11–17 08: 59: 00.086 12 RX-anmodninger færdige til optioner: 3863ms
11–17 08: 59: 20.018 12 RX-anmodninger færdige til optioner: 3816ms
11–17 08: 59: 39.143 12 RX-anmodninger afsluttet for optioner: 3628ms
11–17 08: 59: 57.367 12 RX-forespørgsler afsluttet til optioner: 3561ms
11–17 09: 00: 15.758 12 RX Forespørgsler afsluttet til optioner: 3713ms
11–17 09: 00: 39.129 12 RX-anmodninger afsluttet til optioner: 3612ms

Gennemsnitlig runtime 3698,83ms for min RX-løsning.

ATAsync:
11–17 08: 54: 49.277 12 Anmodninger afsluttet om optioner: 4085ms
11–17 08: 55: 37.718 12 Anmodninger afsluttet om optioner: 3980ms
11–17 08: 55: 59.819 12 Anmodninger afsluttet om optioner: 3925ms
11–17 08: 56: 20.861 12 Anmodninger afsluttet om optioner: 3736ms
11–17 08: 56: 41.438 12 Anmodninger afsluttet om optioner: 3549ms
11–17 08: 57: 01.110 12 Anmodninger afsluttet om optioner: 3833ms

Gennemsnitlig runtime 3851.33ms for min AsyncTask-løsning.

Brug af RX gør efter min mening ringe eller ingen forskel i driftstider. Hvad der udgør runtime er handlingen inden for den baggrundsopgave, du prøver at beregne.

Hvad RX dog giver dig, er vedligeholdelighed. Din kode er meget lettere at holde sig ajour, mindre tilbøjelig til fejl. Du kan logisk udskrive din løsning i den samme rækkefølge, som den køres i. Dette er en enorm bonus til logisk at få tag i koden, når du hopper i kulde.

Selvom det stadig er ok at bare bruge AsyncTasks, og alle kan gøre, hvad de normalt gør, går introduktion af RX ud over bare baggrundsopgaver. Du får en verden af ​​nye muligheder og magtfulde måder, du funktionelt kan røre din arbejdsgang og operationer på. Der er mange mange ting, du kan gøre med RX, som du ikke kan gøre med AysncTasks.

Bare se på det ekstra arbejde, jeg skal gøre for at få min AsyncTask-version til at fungere. Jeg har tilsløret koden for ikke at vise noget virksomhedsfølsomt. Dette er en hån ved min faktiske kode.

AsyncTask version:

indstillinger i offentlig klasseKameraRequester implementerer IOptionRepository {
    ATAsyncTask currentTask;
    boolsk er annulleret;
    endelig HttpConnector-stik;
    privat lang starttid;
    public OptionsCameraRequester (String ipAddress) {
        this.connector = ny HttpConnector (ipAddress);
    }
    offentlig annullering annullere () {
        isCanceled = sandt;
        if (nuværende opgave! = null) {
            currentTask.cancel (sand);
            nuværende opgave = null;
        }
    }
    offentligt ugyldigt getOptions (tilbagekald af tilbagekald) {
        hvis (isCanceled) vender tilbage;
        startTime = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "Forespørgsler startet om indstillinger");
        Iterator  iterator =
            CameraOption.getAllPossibleOptions () iterator ().;
        requestOption (iterator, tilbagekald);
    }
    ugyldig anmodningOption (endelig Iterator  iterator,
                       sidste tilbagekald tilbagekald) {
        if (! iterator.hasNext ()) {
            sidste lang tid = System.currentTimeMillis ();
            Log.i (MyLog.TAG, "Forespørgsler afsluttet for indstillinger:" +
                    (System.currentTimeMillis () - startTime) +
                    "Frk");
            Vend tilbage;
        }
        endelig CameraOption-option = iterator.next ();
        endelig AsyncTask  task =
                ny AsyncTask  () {
                    CameraOption doInBackground (V ..) {
                        JSONObject resultat =
                            connector.getOption (option.getName ());
                        if (resultat == null) {
                            returnere null;
                        } andet {
                            // Gør noget arbejde med JSONObject
                        }
                        returret;
                    }
                    void onPostExecute (CameraOption option) {
                        OptionsCameraRequester.this.currentTask =
                            nul;
                        if (option! = null) {
                            callback.onOptionAvailable (ekstraudstyr);
                        }
                        hvis (! er annulleret) {
                            requestOption (iterator, tilbagekald);
                        }
                    }
                };
        task.execute ();
        nuværende opgave = opgave;
    }
}

RX version:

indstillinger i offentlig klasseKameraRequester implementerer IOptionRepository {
    endelig HttpConnector-stik;
    Abonnement getOptions Abonnement;
    public OptionsCameraRequester (String ipAddress) {
        this.connector = ny HttpConnector (ipAddress);
    }
    offentlig annullering annullere () {
        if (getOptionsSubscription! = null) {
            getOptionsSubscription.unsubscribe ();
            getOptionsSubscription = null;
        }
    }
    // Jeg bruger tilbagekaldet, så jeg kan overholde det samme system
    // interface og hold RX-koden indeholdt i netop dette
    // klasse.

    offentligt ugyldigt getOptions (endelig tilbagekald af tilbagekald)
        sidste lang tid = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "RX-anmodninger startet for indstillinger");
        getOptionsSubscription =
        Observable.from (CameraOption.getAllPossibleOptions ())
            // Anmod om hver indstilling fra kameraet
            .map (ny Func1  () {
                    
                offentligt CameraOption-opkald (CameraOption-indstilling) {
                    JSONObject object =
                        connector.getOption (option.getName ());
                    if (objekt == null) {
                        cameraOption.setAvailable (falsk);
                    } andet {
                        // Arbejd med JSONObject to init-indstillingen
                    }
                    returkamera Option;
               }
            })
            // Filtrer indstillinger, der ikke understøttes
            .filter (ny Func1  () {
                    
                offentlig boolsk opkald (CameraOption cameraOption) {
                    return cameraOption.isAvailable ();
                }
            })
            // Angiv, at trådene er udført og modtaget den
            .observeOn (AndroidSchedulers.mainThread ())
            .subscribeOn (Schedulers.newThread ())
            // Frakald hver mulighed, når den er klar
            .ubmelding (ny abonnent  () {
         
                offentligt ugyldigt onCompleated () {
                   getOptionsSubscription = null;
                   Log.i (MyLog.TAG, "RX-anmodninger afsluttet:" +
                        (System.currentTimeMillis () - tid) + "ms");
                }
                public void onError (Throwable e) {
                   MyLog.eLogErrorAndReport (MyLog.TAG, e);
                   callback.onError ();
                }
                public void onNext (CameraOption cameraOption) {
                    callback.onOptionAvailable (cameraOption);
                }
           });
    }
}

Mens RX-koden ser længere ud, er der ikke længere afhængig af at styre en iterator. Der er ingen brud på et rekursivt funktionsopkald, der skifter tråde. Der er ingen boolesk værdi til at spore, at opgaverne annulleres. Alt er skrevet i den rækkefølge, det udføres.