AsyncTask vs. RX - У невялікім выпадку выкарыстання

Нядаўна я працаваў над задачай, дзе мне трэба было паслядоўна сінхранізаваць 12 сеткавых запытаў. RESTful JSON api запыты, адзін за адным.

Я спецыяльна працаваў над запытам варыянтаў з камеры, дзе размяшчаўся лакальны API, з якім ваша прылада Android можа падключацца да Wi-Fi. API верне, якія параметры былі даступныя і якія значэнні можна абраць для кожнага з гэтых варыянтаў.

Гнуткі API дазваляе вам запытаць наяўнасць некалькіх варыянтаў адначасова з адным запытам. У мяне было 12 варыянтаў, якія мяне цікавілі, налады экспазіцыі і дыяфрагма, і такія варыянты.

Адзіная праблема заключалася ў тым, што калі варыянт быў недаступны, API камеры вярнуў 404 як адказ. І нават калі я прасіў некалькі! Такім чынам, калі б толькі адзін варыянт з 12-ці адсутнічаў, вы атрымліваеце 404 і нічога не ведаеце пра астатнія 11. Ну, гэта бескарысна, мне прыйшлося перайсці на запыт кожнага з варыянтаў.

Я прымаю кожны з гэтых варыянтаў і ўкладваю іх у RecyclerView, каб карыстальнік мог выбраць іх налады праз спінер.

Раней я выкарыстоўваў RX, у прыватнасці RXJava2, у праграмах, над якімі я працаваў. Але ў мяне не было магчымасці выкарыстоўваць яе ў паўсядзённым супрацоўніцтве з працоўным сталом прадпрыемства.

Уключэнне бібліятэк у базавую коду прадпрыемства, якую я знайшоў, можа быць больш складанай задачай, чым сітуацыі запуску і свабоднага запуску. Справа не ў тым, што бібліятэкі не вялікія і не ствараюць праблем. Справа ў тым, што ў рашэннях удзельнічае шмат людзей, і вам трэба добра прадаць розныя спосабы кадавання.

Я, магчыма, не самы лепшы ў продажы ідэй, але я імкнуся ўдасканальвацца!

Ну вось, у мяне ёсць цудоўны прыклад сітуацыі, калі наяўнасць RX зробіць для мяне гэтыя 12 запытаў больш простымі і даступнымі.

Мы звычайна выкарыстоўвалі AsyncTasks для працы ў фонавым рэжыме, як гэта робіцца ў гэтым дадатку на працягу доўгага часу. Спадчына застаецца на працягу ўсяго жыцця, як толькі вы вырашыце на тэхналогію, яна будзе прытрымлівацца гэтага прыкладання некаторы час. Яшчэ адна прычына, па якой рашэнні па тэзісах прымаюцца несур'ёзна.

Мне, як правіла, падабаецца спрабаваць новыя рэчы і заставацца пярэднім краем.

Яшчэ лепш, што мяне прымусіла параўнаць і прыкладам RX і AsyncTask, тое, што бібліятэка трэцяй партыі, якую мы выкарыстоўвалі, залежала ад версіі RXJava 1.

Нізка і вось увесь гэты час ён сядзеў у нашай базавай кодзе, чакаючы, калі яго выкарыстаюць.

Такім чынам, я і са сваім супрацоўнікам узгаднілі заданне зрабіць тэст, каб праверыць розніцу для гэтай задачы паміж выкарыстаннем RX і AsyncTask.

Аказваецца, тэрміны абсалютна нязначныя! Будзем спадзявацца, што гэта развее любыя міфы пра тое, што для невялікіх фонавых задач выкарыстанне AsyncTask павольна. Пра гэта мне рэгулярна паведамляюць з розных крыніц. Цікава, што я знайшоў бы, калі б рабіў вялікія тэсты.

Я зрабіў невялікі набор узораў. Запуск маёй дзейнасці з абодвума рашэннямі 6 разоў, і вось што я атрымаў:

RX:
11–17 08: 59: 00.086 12 Запыты на RX для варыянтаў: 3863 мс
11–17 08: 59: 20,018 12 Запыты RX для варыянтаў: 3816 мс
11–17 08: 59: 39,143 12 Запыты на RX для варыянтаў: 3628 мс
11–17 08: 59: 57,367 12 Запыты RX для варыянтаў: 3561 мс
11–17 09: 00: 15,758 12 запытаў на RX для варыянтаў: 3713 мс
11–17 09: 00: 39.129 12 Запыты на RX для варыянтаў: 3612 мс

Сярэдняе выкананне для майго рашэння RX 3698,83ms.

ATAsync:
11–17 08: 54: 49,277 12 Запыты на варыянты: 4085 мс
11–17 08: 55: 37,718 12 Запыты на варыянты: 3980 мс
11–17 08: 55: 59,819 12 Запыты на варыянты: 3925 мс
11–17 08: 56: 20,861 12 Запыты скончаны для варыянтаў: 3736 мс
11–17 08: 56: 41,438 12 Запыты на варыянты: 3549 мс
11–17 08: 57: 01,110 12 Запыты на варыянты: 3833 мс

Сярэдняе выкананне для майго рашэння AsyncTask 3851,33ms.

На мой погляд, выкарыстанне RX практычна не мяняе працягласці выканання. Сапраўды, што складае час выканання, гэта аперацыя ўнутры фонавай задачы, якую вы спрабуеце вылічыць.

Тое, што RX дае вам, - гэта рамонты. Ваш код значна прасцей абнаўляць, менш схільны памылак. Вы можаце лагічна выпісаць сваё рашэнне ў тым самым паслядоўным парадку, у якім выкарыстоўваецца. Гэта велізарны бонус за лагічны паглыбленне кода пры скачках у мароз.

Хоць усё яшчэ нармальна выкарыстоўваць AsyncTasks, і ўсе могуць рабіць тое, што звычайна робяць, увядзенне RX выходзіць за рамкі асноўных задач. Вы атрымліваеце свет новых магчымасцей і магутных спосабаў, якія вы можаце функцыянальна накіраваць на працу і аперацыі. З RX можна шмат чаго зрабіць, што з AysncTasks нельга.

Проста паглядзіце на дадатковую працу, якую мне трэба зрабіць, каб працаваць мая версія AsyncTask. Я збянтэжыў гэты код, каб не паказваць нічога адчувальнага для кампаніі. Гэта здзек з майго фактычнага кода.

Версія AsyncTask:

Публічны клас OptionsCameraRequester рэалізуе IOptionRepository {
    ATAsyncTask currentTask;
    булевы;
    канчатковы раз'ём HttpConnector;
    прыватны доўгі startTime;
    public OptionsCameraRequester (String ipAddress) {
        this.connector = новы HttpConnector (ipAddress);
    }
    публічная ануляцыя ануляваць () {
        isCanceled = true;
        калі (currentTask! = null) {
            currentTask.cancel (праўда);
            currentTask = null;
        }
    }
    адкрытыя несапраўдныя getOptions (зваротны званок званка) {
        калі (isCanceled) вяртанне;
        startTime = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "Запыты пачаты для варыянтаў");
        Ітэратар  ітэратар =
            CameraOption.getAllPossibleOptions (). Iterator ();
        requestOption (ітэратар, зваротны званок);
    }
    ануляванне requestOption (канчатковы ітэратар  ітэратар,
                       канчатковы зваротны званок) {
        калі (! iterator.hasNext ()) {
            final long long = System.currentTimeMillis ();
            Log.i (MyLog.TAG, "Запыты завершаны для варыянтаў:" +
                    (System.currentTimeMillis () - startTime) +
                    "Спадарыня");
            вяртанне;
        }
        канчатковы варыянт CameraOption = iterator.next ();
        final AsyncTask  задача =
                новы AsyncTask  () {
                    CameraOption doInBackground (V ..) {
                        Вынік JSONObject =
                            connector.getOption (option.getName ());
                        калі (вынік == нулявы) {
                            вярнуць нуль;
                        } яшчэ {
                            // Зрабіце нейкую працу з JSONObject
                        }
                        варыянт вяртання;
                    }
                    несапраўдны onPostExecute (варыянт CameraOption) {
                        OptionsCameraRequester.this.currentTask =
                            нулявы;
                        калі (параметр! = нулявы) {
                            callback.onOptionAvailable (опцыя);
                        }
                        калі (! адменена) {
                            requestOption (ітэратар, зваротны званок);
                        }
                    }
                };
        task.execute ();
        currentTask = задача;
    }
}

Версія RX:

Публічны клас OptionsCameraRequester рэалізуе IOptionRepository {
    канчатковы раз'ём HttpConnector;
    Падпіска getOptionsSubscription;
    public OptionsCameraRequester (String ipAddress) {
        this.connector = новы HttpConnector (ipAddress);
    }
    публічная ануляцыя ануляваць () {
        калі (getOptionsSubscription! = null) {
            getOptionsSubscription.unsubscribe ();
            getOptionsSubscription = null;
        }
    }
    // Я выкарыстоўваю зваротны званок, каб я мог прытрымлівацца той жа сістэмы
    // Інтэрфейс і захаваць код RX, які змяшчаецца толькі да гэтага
    // клас.

    адкрытыя несапраўдныя getOptions (канчатковы зваротны званок званка) {
        final long long = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "Запыты RX для параметраў");
        getOptionsSubscription =
        Observable.from (CameraOption.getAllPossibleOptions ())
            // Запыт кожнай опцыі з камеры
            .map (новы Func1  () {
                    
                адкрыты выклік CameraOption (опцыя CameraOption) {
                    Аб'ект JSONObject =
                        connector.getOption (option.getName ());
                    калі (object == null) {
                        cameraOption.setAvailable (false);
                    } яшчэ {
                        // Працуйце з варыянтам JSONObject to init
                    }
                    вяртанне cameraOption;
               }
            })
            // Адфільтраваць параметры, якія не падтрымліваюцца
            .filter (новы Func1  () {
                    
                публічны булевы выклік (CameraOption cameraOption) {
                    вярнуцца cameraOption.isAvailable ();
                }
            })
            // Абвясціць працу тэмы зроблена і атрымана далей
            .observeOn (AndroidSchedulers.mainThread ())
            .subscribeOn (Schedulers.newThread ())
            // Перадайце кожны варыянт па меры гатоўнасці
            . падпіска (новы падпісчык  () {
         
                публічная несапраўдная onCompleted () {
                   getOptionsSubscription = null;
                   Log.i (MyLog.TAG, "Запыты RX завершаны:" +
                        (System.currentTimeMillis () - час) + "ms");
                }
                публічная несапраўдная onError (Throwable e) {
                   MyLog.eLogErrorAndReport (MyLog.TAG, e);
                   callback.onError ();
                }
                адкрытая пустэча onNext (CameraOption cameraOption) {
                    callback.onOptionAvailable (cameraOption);
                }
           });
    }
}

Хоць код RX выглядае даўжэй, у кіраванні ітэратарам больш не залежаць. Адбываецца рэкурсіўны выклік функцыі, які пераключае тэмы. Няма булевага значэння для адсочвання таго, што задачы адменены. Усё напісана ў парадку, у якім яно выканана.