AsyncTask vs. RX - V malém případě použití

Nedávno jsem pracoval na úkolu, kde jsem musel postupně synchronizovat 12 síťových požadavků. RESTful JSON api request, one after next.

Pracoval jsem konkrétně na vyžádání možností z kamery, která hostovala místní rozhraní API, ke kterému se vaše zařízení Android mohlo připojit přes wifi. Rozhraní API vrátí, které možnosti byly k dispozici a jaké hodnoty by mohly být vybrány pro každou z těchto možností.

Flexibilní API vám umožnilo dotazovat dostupnost více možností najednou s jedním požadavkem. Měl jsem 12 možností, které mě zajímaly, nastavení expozice a clony a podobné možnosti.

Jediným problémem bylo to, že pokud nebyla k dispozici možnost, vrátilo API kamery 404 jako odpověď. A i když jsem požádal o více! Pokud tedy chyběla pouze jedna z dvanácti, dostali byste 404 a nevíte nic o ostatních 11. No to je zbytečné, musel jsem se přepnout na vyžádání každé možnosti, jednu po druhé.

Užíval jsem každou z těchto možností a vložil je do RecyclerView, aby si uživatel mohl vybrat nastavení pomocí číselníku.

RX jsem dříve používal, konkrétně RXJava2, v aplikacích, na kterých jsem pracoval. Ale ještě jsem neměl příležitost ji použít ve své každodenní spolupráci v kanceláři.

Přivedení knihoven do podnikové kódové základny, kterou jsem našel, může být náročnější než situace při zakládání nebo na volné noze. Není to tak, že knihovny nejsou velké nebo nezpůsobují problémy. Je to, že se zde mnoho lidí podílí na rozhodování a musíte být dobří v prodeji různých způsobů kódování.

Možná nejsem zatím nejlepší v prodeji nápadů, ale snažím se zlepšit!

Tady mám dokonalý příklad situace, kdy by s RX bylo pro mě těchto 12 požadavků jednodušší a lépe udržovatelné.

Obvykle jsme používali AsyncTasks pro naši práci na pozadí, jak se to dělá v této aplikaci po dlouhou dobu. Legacy má životnost, jakmile se rozhodnete pro technologii, bude tuto aplikaci chvíli sledovat. Dalším důvodem, proč tato rozhodnutí nejsou učiněna lehce.

Já, mám sklon zkoušet nové věci a zůstat na špičce.

Ještě lepší mě přivedlo do bodu, kdy mohu skutečně provést srovnání a příkladem RX a AsyncTask, byla skutečnost, že knihovna třetích stran, kterou jsme používali, byla závislá na RXJava verzi 1.

Nízko a hle, celou tu dobu tam seděl v naší kódové základně a čekal na použití.

Já a já se mým souhlasem spolupracovníků jsme tedy stanovili, že uděláme benchmark, abychom otestovali rozdíl pro tento jediný úkol mezi použitím RX a AsyncTask.

Ukázalo se, že načasování je naprosto zanedbatelné! Doufejme, že to rozptýlí všechny mýty, že pro malé úkoly na pozadí je používání AsyncTask pomalé. Říká mi to docela pravidelně z více zdrojů. Zajímalo by mě, co bych našel, kdybych udělal větší testy.

Udělal jsem malou sadu vzorků. S mojí činností u obou řešení šestkrát a to je to, co jsem dostal:

RX:
11–17 08: 59: 00,086 12 Požadavky na RX byly dokončeny pro možnosti: 3863 ms
11–17 08: 59: 20,018 12 Požadavky na RX byly dokončeny pro možnosti: 3816 ms
11–17 08: 59: 39,143 12 Požadavky na RX byly dokončeny pro možnosti: 3628 ms
11–17 08: 59: 57,367 12 Požadavky na RX byly dokončeny pro možnosti: 3561 ms
11–17 09: 00: 15.758 12 Požadavky na RX byly dokončeny pro možnosti: 3713ms
11–17 09: 00: 39,129 12 Požadavky RX byly dokončeny pro možnosti: 3612 ms

Průměrná doba běhu 3698,83 ms pro mé řešení RX.

ATAsync:
11–17 08: 54: 49 277 12 Požadavky na možnosti byly dokončeny: 4085 ms
11–17 08: 55: 37,718 12 Požadavky na možnosti dokončeny: 3980 ms
11–17 08: 55: 59,819 12 Požadavky na možnosti dokončeny: 3925 ms
11–17 08: 56: 20,861 12 Požadavky dokončeny pro možnosti: 3736 ms
11–17 08: 56: 41,438 12 Požadavky na možnosti byly dokončeny: 3549 ms
11–17 08: 57: 01.110 12 Požadavky na možnosti byly dokončeny: 3833 ms

Průměrná doba běhu 3851,33 ms pro mé řešení AsyncTask.

Používání RX je podle mého názoru v runtime časech malý až žádný. Opravdu to, co tvoří runtime, je operace uvnitř té úlohy na pozadí, kterou se pokoušíte vypočítat.

Co vám však RX dává, je udržovatelnost. Váš kód je mnohem snazší udržovat aktuální, méně náchylný k chybám. Můžete logicky zapsat své řešení ve stejném pořadí, v jakém je spuštěno. To je obrovský bonus k logickému grokování kódu, když se nacházíte v chladu.

Přestože je stále v pořádku používat AsyncTasks a každý může dělat to, co obvykle dělá, představení RX přesahuje jen úkoly na pozadí. Získáte svět nových příležitostí a výkonných způsobů, jak můžete funkčně rozvinout pracovní postup a operace. S RX můžete dělat spoustu věcí, které s AysncTasks nemůžete dělat.

Stačí se podívat na další práci, kterou musím udělat, aby moje verze AsyncTask fungovala. Kód jsem zmatil, abych neukazoval nic citlivého na společnost. Toto je vysmívat se mému skutečnému kódu.

Verze AsyncTask:

veřejná třída OptionsCameraRequester implementuje IOptionRepository {
    ATAsyncTask currentTask;
    boolean isCanceled;
    finální konektor HttpConnector;
    soukromý dlouhý startTime;
    public OptionsCameraRequester (String ipAddress) {
        this.connector = new HttpConnector (ipAddress);
    }
    veřejné neplatné zrušení () {
        isCanceled = true;
        if (currentTask! = null) {
            currentTask.cancel (true);
            currentTask = null;
        }
    }
    public void getOptions (zpětné zpětné volání) {
        if (isCanceled) return;
        startTime = System.currentTimeMillis ();
        Log.i (MyLog.TAG, „Požadavky byly spuštěny pro možnosti“);
        Iterator  iterator =
            CameraOption.getAllPossibleOptions (). Iterator ();
        requestOption (iterátor, zpětné volání);
    }
    void requestOption (finální iterátor ,
                       závěrečné zpětné zpětné volání) {
        if (! iterator.hasNext ()) {
            konečná dlouhá doba = System.currentTimeMillis ();
            Log.i (MyLog.TAG, "Požadavky dokončeny pro možnosti:" +
                    (System.currentTimeMillis () - startTime) +
                    "slečna");
            vrátit se;
        }
        final CameraOption option = iterator.next ();
        final AsyncTask  task =
                nový AsyncTask  () {
                    CameraOption doInBackground (V ..) {
                        JSONObject result =
                            connector.getOption (option.getName ());
                        if (result == null) {
                            návrat null;
                        } jinde {
                            // Proveďte nějakou práci s JSONObject
                        }
                        možnost návratu;
                    }
                    void onPostExecute (možnost CameraOption) {
                        OptionsCameraRequester.this.currentTask =
                            nula;
                        if (option! = null) {
                            callback.onOptionAvailable (možnost);
                        }
                        if (! isCanceled) {
                            requestOption (iterátor, zpětné volání);
                        }
                    }
                };
        task.execute ();
        currentTask = task;
    }
}

Verze RX:

veřejná třída OptionsCameraRequester implementuje IOptionRepository {
    finální konektor HttpConnector;
    Předplatné getOptionsSubscription;
    public OptionsCameraRequester (String ipAddress) {
        this.connector = new HttpConnector (ipAddress);
    }
    veřejné neplatné zrušení () {
        if (getOptionsSubscription! = null) {
            getOptionsSubscription.unsubscribe ();
            getOptionsSubscription = null;
        }
    }
    // Používám zpětné volání, abych mohl dodržovat stejný systém
    // rozhraní a udržet RX kód obsažený právě v tomto
    // třída.

    veřejné neplatné getOptions (konečné zpětné zpětné volání) {
        konečná dlouhá doba = System.currentTimeMillis ();
        Log.i (MyLog.TAG, „Požadavky RX byly spuštěny pro možnosti“);
        getOptionsSubscription =
        Pozorovatelné z (CameraOption.getAllPossibleOptions ())
            // Vyžádejte každou možnost z kamery
            .map (nová Func1  () {
                    
                veřejné volání CameraOption (možnost CameraOption) {
                    JSONObject object =
                        connector.getOption (option.getName ());
                    if (object == null) {
                        cameraOption.setAvailable (false);
                    } jinde {
                        // Pracujte s možností JSONObject pro zahájení
                    }
                    návrat kamery;
               }
            })
            // Filtrujte možnosti, které nejsou podporovány
            .filter (nový Func1  () {
                    
                veřejné booleovské volání (CameraOption cameraOption) {
                    return cameraOption.isAvailable ();
                }
            })
            // Deklarovat práci podprocesů je hotovo a přijato
            .observeOn (AndroidSchedulers.mainThread ())
            .subscribeOn (Schedulers.newThread ())
            // Předejte každou možnost, jak je připravena
            .subscribe (new Subscriber  () {
         
                public void onCompleted () {
                   getOptionsSubscription = null;
                   Log.i (MyLog.TAG, "Požadavky RX dokončeny:" +
                        (System.currentTimeMillis () - time) + "ms");
                }
                public void onError (Throwable e) {
                   MyLog.eLogErrorAndReport (MyLog.TAG, e);
                   callback.onError ();
                }
                public void onNext (CameraOption cameraOption) {
                    callback.onOptionAvailable (cameraOption);
                }
           });
    }
}

Zatímco kód RX vypadá déle, již se nespoléhá na správu iterátoru. Při rekurzivním volání funkce, které přepíná vlákna, nedochází k žádnému přerušení. Neexistuje žádná logická hodnota, která by sledovala zrušení úkolů. Vše je zapsáno v pořadí, v jakém je provedeno.