AsyncTask vs. RX - Σε μια μικρή περίπτωση χρήσης

Πρόσφατα εργαζόμουν σε μια εργασία όπου έπρεπε να συγχρονίσω 12 αιτήματα δικτύου με διαδοχικό τρόπο. Τα πιο απαιτητικά 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 ολοκληρώθηκαν για επιλογές: 3863ms
11-17 08: 59: 20.018 12 Οι αιτήσεις RX ολοκληρώθηκαν για επιλογές: 3816ms
11-17 08: 59: 39.143 12 Οι αιτήσεις RX ολοκληρώθηκαν για επιλογές: 3628ms
11-17 08: 59: 57.367 12 Οι αιτήσεις RX ολοκληρώθηκαν για επιλογές: 3561ms
11-17 09: 00: 15.758 12 Οι αιτήσεις RX ολοκληρώθηκαν για επιλογές: 3713ms
11-17 09: 00: 39.129 12 Οι αιτήσεις RX ολοκληρώθηκαν για επιλογές: 3612ms

Μέσος χρόνος εκτέλεσης 3698.83ms για τη λύση RX μου.

ATAsync:
11-17 08: 54: 49.277 12 Οι αιτήσεις ολοκληρώθηκαν για επιλογές: 4085ms
11-17 08: 55: 37.718 12 Οι αιτήσεις ολοκληρώθηκαν για επιλογές: 3980ms
11-17 08: 55: 59.819 12 Οι αιτήσεις ολοκληρώθηκαν για επιλογές: 3925ms
11-17 08: 56: 20.861 12 Οι αιτήσεις ολοκληρώθηκαν για επιλογές: 3736ms
11-17 08: 56: 41.438 12 Οι αιτήσεις ολοκληρώθηκαν για επιλογές: 3549ms
11-17 08: 57: 01.110 12 Οι αιτήσεις ολοκληρώθηκαν για επιλογές: 3833ms

Μέση διάρκεια εκτέλεσης 3851,33ms για τη λύση AsyncTask μου.

Χρησιμοποιώντας RX κάνει ελάχιστη διαφορά στις ώρες εκτέλεσης κατά τη γνώμη μου. Πραγματικά αυτό που αποτελεί το runtime είναι η λειτουργία μέσα σε αυτή την εργασία φόντου που προσπαθείτε να υπολογίσετε.

Αυτό που σας δίνει το RX είναι όμως η συντηρησιμότητα. Ο κώδικας σας είναι πολύ πιο εύκολος να ενημερώνεστε, λιγότερο επιρρεπείς σε σφάλματα. Μπορείτε να γράψετε λογικά τη λύση σας με την ίδια διαδοχική σειρά που τρέχει μέσα. Αυτό είναι ένα τεράστιο επίδομα για λογικό grokking τον κώδικα, όταν το άλμα στο κρύο.

Ενώ είναι ακόμα εντάξει να χρησιμοποιείτε μόνο AsyncTasks και όλοι μπορούν να κάνουν αυτό που συνήθως κάνουν, η εισαγωγή RX ξεπερνά μόνο τα καθήκοντα του περιβάλλοντος. Παίρνετε έναν κόσμο νέων ευκαιριών και ισχυρών τρόπων που μπορείτε να διοχετεύσετε λειτουργικά τη ροή εργασίας και τις λειτουργίες σας. Υπάρχουν πολλά πολλά πράγματα που μπορείτε να κάνετε με το RX που δεν μπορείτε να κάνετε με το AysncTasks.

Απλά κοιτάξτε το επιπλέον έργο που πρέπει να κάνω για να φτάσω την έκδοση του AsyncTask μου. Έχω αποκαλύψει τον κώδικα για να μην δείξω τίποτα ευαίσθητο στην εταιρεία. Αυτό είναι ένα ψεύτικο του πραγματικού μου κώδικα.

Έκδοση AsyncTask:

δημόσια class OptionsCameraRequester υλοποιεί IOptionRepository {
    ATAsyncTask currentTask;
    boolean isCanceled;
    τελική υποδοχή σύνδεσης HttpConnector.
    ιδιωτική μακρά εκκίνηση.
    δημόσιο OptionsCameraRequester (String ipAddress) {
        this.connector = νέο HttpConnector (ipAddress);
    }}
    δημόσιο κενό ακυρώστε () {
        isCanceled = true;
        αν (currentTask! = null) {
            currentTask.cancel (αλήθεια);
            currentTask = null;
        }}
    }}
    δημόσιο κενό getOptions (Επανάκληση επανάκλησης) {
        αν είναι (isCanceled) επιστροφή;
        startTime = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "Οι αιτήσεις ξεκίνησαν για επιλογές").
        Iterator  iterator =
            CameraOption.getAllPossibleOptions () .Iterator ();
        requestOption (iterator, callback);
    }}
    άκυρη requestOption (τελικός Iterator  iterator,
                       τελική επανάκληση επανάκλησης) {
        αν (! iterator.hasNext ()) {
            τελικό μεγάλο χρονικό διάστημα = System.currentTimeMillis ();
            Log.i (MyLog.TAG, "Οι αιτήσεις ολοκληρώθηκαν για τις επιλογές:" +
                    (System.currentTimeMillis () - startTime) +
                    "Κυρία");
            ΕΠΙΣΤΡΟΦΗ;
        }}
        τελική επιλογή CameraOption = iterator.next ();
        τελικό AsyncTask <Άκυρη, άκυρη, CameraOption> εργασία =
                Νέο AsyncTask <Άκυρο, άκυρο, CameraOption> () {
                    CameraOption doInBackground (V ..) {
                        JSONObject αποτέλεσμα =
                            connector.getOption (option.getName ());
                        αν (αποτέλεσμα == null) {
                            επιστροφή null;
                        } else {
                            // Λειτουργούν με το JSONObject
                        }}
                        επιλογή επιστροφής;
                    }}
                    void onPostExecute (επιλογή CameraOption) {
                        ΕπιλογέςCameraRequester.this.currentTask =
                            μηδενικό;
                        αν (επιλογή! = null) {
                            callback.onOptionAvailable (επιλογή).
                        }}
                        αν (! isCanceled) {
                            requestOption (iterator, callback);
                        }}
                    }}
                },
        task.execute ();
        currentTask = task;
    }}
}}

Έκδοση RX:

δημόσια class OptionsCameraRequester υλοποιεί IOptionRepository {
    τελική υποδοχή σύνδεσης HttpConnector.
    Εγγραφή getOptionsSubscription;
    δημόσιο OptionsCameraRequester (String ipAddress) {
        this.connector = νέο HttpConnector (ipAddress);
    }}
    δημόσιο κενό ακυρώστε () {
        αν (getOptionsSubscription! = null) {
            getOptionsSubscription.unsubscribe ();
            getOptionsSubscription = null;
        }}
    }}
    // Χρησιμοποιώ την επιστροφή κλήσης για να μπορώ να τηρήσω το ίδιο σύστημα
    // interface και διατηρήστε τον κώδικα RX που περιέχεται σε αυτό ακριβώς
    // class.

    δημόσιο κενό getOptions (τελική επανάκληση Callback) {
        τελικό μεγάλο χρονικό διάστημα = System.currentTimeMillis ();
        Log.i (MyLog.TAG, "Τα αιτήματα RX ξεκίνησαν για επιλογές").
        getOptionsSubscription =
        Παρατηρήσιμη.από (CameraOption.getAllPossibleOptions ())
            // Ζητήστε κάθε επιλογή από τη φωτογραφική μηχανή
            .map (νέο Func1  () {{
                    
                δημόσια κλήση CameraOption (επιλογή CameraOption) {
                    Αντικείμενο JSONObject =
                        connector.getOption (option.getName ());
                    αν (αντικείμενο == null) {
                        cameraOption.setAvailable (false);
                    } else {
                        // Να εργαστείτε με την επιλογή JSONObject στην επιλογή init
                    }}
                    επιστροφή φωτογραφικής μηχανής.
               }}
            })
            // Φιλτράρισμα επιλογών που δεν υποστηρίζονται
            .filter (νέο Func1  () {
                    
                δημόσια κλήση Boolean (CameraOption cameraOption) {
                    επιστρέφει cameraOption.isAvailable ();
                }}
            })
            // Διακηρύσσει ότι οι εργασίες του νήματος γίνονται και παραλαμβάνονται
            .observeOn (AndroidSchedulers.mainThread ())
            .subscribeOn (Schedulers.newThread ())
            // Περάστε κάθε επιλογή όπως είναι έτοιμη
            . subscribe (νέος συνδρομητής  () {{
         
                δημόσιο κενό onCompleted () {)
                   getOptionsSubscription = null;
                   Log.i (MyLog.TAG, "Τα αιτήματα RX τελειώθηκαν:" +
                        (System.currentTimeMillis () - χρόνος) + "ms").
                }}
                δημόσιο κενό onError (Throwable e) {
                   MyLog.eLogErrorAndReport (MyLog.TAG, ε);
                   callback.onError ();
                }}
                δημόσιο κενό στοΝέα (CameraOption cameraOption) {
                    callback.onOptionAvailable (cameraOption);
                }}
           });
    }}
}}

Ενώ ο κώδικας RX φαίνεται μακρύτερος, δεν υπάρχει πλέον εξάρτηση από τη διαχείριση ενός iterator. Δεν υπάρχει σπάσιμο από μια κλήση επαναλαμβανόμενης λειτουργίας που μετατρέπει τα νήματα. Δεν υπάρχει τιμή boolean για να παρακολουθείτε ότι οι εργασίες ακυρώνονται. Όλα γράφονται με τη σειρά εκτέλεσης.