example - javascript tutorial




Richieste di dosaggio per ridurre al minimo lo scarico delle celle (4)

  • SetInterval mantiene il telefono sempre attivo? È una brutta cosa da fare? C'è un modo migliore per non farlo?

Da un ambiente iPhone 4, iOS 6.1.0 Safari:

A ha scritto un'app con un timer per il conto alla rovescia che ha aggiornato il testo di un elemento su intervalli di un secondo. L'albero DOM aveva una complessità media. L'app era una calcolatrice relativamente semplice che non faceva alcun AJAX. Tuttavia, ho sempre avuto un vago sospetto che quei riflussi all'unisono mi stessero uccidendo. La mia batteria sembrava esaurirsi abbastanza rapidamente, ogni volta che la lasciavo accesa su un tavolo, con Safari sulla pagina web dell'app.

E c'erano solo due timeout in quell'app. Ora, non ho alcuna prova quantificabile del fatto che i timeout stiano scaricando la mia batteria, ma perdere circa il 10% ogni 45 minuti da questo calcolatore dopey è stato un po 'snervante. (Chi lo sa, forse era il controluce.)

A tale proposito: è possibile creare un'app di test che esegua AJAX a intervalli, altre su intervalli, ecc. E confrontare il modo in cui ciascuna funzione scarica la batteria in condizioni simili. Ottenere un ambiente controllato potrebbe essere complicato, ma se c'è una differenza abbastanza grande nello scarico, allora anche le condizioni di test "imperfette" produrranno risultati abbastanza evidenti da consentirvi di trarre una conclusione.

Tuttavia, ho scoperto una cosa interessante su come iOS 6.1.0 Safari gestisce i timeout:

  • I timeout non eseguono i loro callback se si spegne lo schermo.
  • Di conseguenza, i timeout a lungo termine "mancheranno nel segno".

Se il timer della mia app doveva visualizzare l'ora corretta (anche dopo aver chiuso e riaperto lo schermo), non potevo percorrere la via facile e fare secondsLeft -= 1 . Se avessi spento lo schermo, il secondo secondsLeft (relativo al mio orario di partenza) sarebbe stato "dietro", e quindi errato. (Il callback setTimeout non è stato eseguito mentre lo schermo era spento.)

La soluzione era che dovevo ricalcolare timeLeft = fortyMinutes - (new Date().getTime() - startTime) su ogni intervallo.

Inoltre, il timer della mia app doveva passare dal verde, al lime, al giallo, al rosso, avvicinandosi alla scadenza. Poiché, a questo punto, ero preoccupato per l'efficienza del mio intervallo-codice, sospettavo che sarebbe stato meglio "pianificare" i cambiamenti di colore per il loro tempo appropriato (lime: 20 minuti dopo l'ora di inizio, giallo: 30 minuti, rosso: 35) (questo sembrava preferibile a un quadruplo - disuguaglianza - verifica su ogni intervallo, che sarebbe futile il 99% delle volte).

Tuttavia, se avessi programmato un tale cambio di colore e lo schermo del mio telefono fosse spento al momento della destinazione, allora quel cambiamento di colore non sarebbe mai avvenuto.

La soluzione era controllare, in ciascun intervallo, se il tempo trascorso dall'ultimo aggiornamento del timer di 1 secondo era stato " >= 2 secondi". (In questo modo, l'app poteva sapere se il mio telefono aveva il suo schermo spento, era in grado di capire quando era "caduto indietro".) A quel punto, se necessario, avrei "forzatamente" applicato un cambio di colore e un programma il prossimo.

(Inutile dire che in seguito ho rimosso il cambia colori ...)

Quindi, credo che ciò confermi la mia affermazione

iOS 6.1.0 Safari non esegue le funzioni di callback setTimeout se lo schermo è spento.

Quindi tienilo a mente quando "pianifichi" le tue chiamate AJAX, perché probabilmente anche questo comportamento sarà influenzato da questo comportamento.

E, usando la mia proposta, posso rispondere alla tua domanda:

  • Almeno per iOS, sappiamo che setTimeout dorme mentre lo schermo è spento.
  • Così setTimeout non darà al tuo telefono "incubi" ("tienilo sveglio").
  • Questo tipo di approccio vale anche la pena? Ci sono così tante cose contemporaneamente in uno smartphone moderno, che se la mia app non usa la cella, sicuramente un'altra app lo è. Javascript non può rilevare se la cella è attiva o meno, quindi perché preoccuparsi? Vale la pena preoccuparsi?

Se riesci a far funzionare correttamente questa implementazione, sembra che ne varrà la pena.

Dovrai sostenere la latenza per ogni richiesta AJAX effettuata, che rallenterà la tua app in una certa misura. (Dopo tutto, la latenza è la rovina del tempo di caricamento della pagina). In tal modo otterrete sicuramente un certo guadagno raggruppando le richieste. Estendere $ .ajax in modo tale da poter "batch" le richieste avrà sicuramente qualche merito.

Questo articolo ha colpito la parte superiore di HackerNews recentemente: http://highscalability.com/blog/2013/9/18/if-youre-programming-a-cell-phone-like-a-server-youre-doing.html#

In cui afferma:

La radio cellulare è uno dei più grandi scarichi della batteria su un telefono. Ogni volta che invii i dati, non importa quanto piccoli, la radio viene accesa per 20-30 secondi. Ogni decisione presa deve basarsi sulla riduzione al minimo del numero di volte in cui la radio si accende. La durata della batteria può essere notevolmente migliorata cambiando il modo in cui le tue app gestiscono i trasferimenti di dati. Gli utenti vogliono i loro dati ora, il trucco sta bilanciando l'esperienza dell'utente con il trasferimento dei dati e riducendo al minimo l'utilizzo di energia. Un equilibrio viene raggiunto dalle app che raggruppano accuratamente tutti i trasferimenti intermittenti e ripetitivi insieme e quindi anticipano in modo aggressivo i trasferimenti intermittenti.

Vorrei modificare $.ajax per aggiungere un'opzione come "non è necessario che sia fatto in questo momento , basta fare questa richiesta quando viene lanciata un'altra richiesta". Quale sarebbe un buon modo per fare questo?

Ho iniziato con questo:

(function($) {
    var batches = [];
    var oldAjax = $.fn.ajax;
    var lastAjax = 0;
    var interval = 5*60*1000; // Should be between 2-5 minutes
    $.fn.extend({batchedAjax: function() {
        batches.push(arguments);
    }});
    var runBatches = function() {
        var now = new Date().getTime();
        var batched;
        if (lastAjax + interval < now) {
            while (batched = batches.pop()) {
                oldAjax.apply(null, batched);
            }
        }
    }
    setInterval(runBatches, interval);
    $.fn.ajax = function() {
        runBatches();
        oldAjax.apply(null, arguments);
        lastAjax = now;
    };
})(jQuery);

Non posso dire con la formulazione del foglio, immagino che un buon "intervallo" di batch sia di 2-5 minuti, quindi ho usato solo 5.

Questa è una buona implementazione?

  • Come posso rendere questa una vera modifica del solo metodo {batchable:true} , aggiungendo {batchable:true} al metodo? Non l'ho nemmeno capito.

  • setInterval mantiene il telefono sempre attivo? È una brutta cosa da fare? C'è un modo migliore per non farlo?

  • Ci sono altre cose qui che farebbero scaricare la batteria più velocemente?

  • Questo tipo di approccio vale anche la pena? Ci sono così tante cose contemporaneamente in uno smartphone moderno, che se la mia app non usa la cella, sicuramente un'altra app lo è. Javascript non può rilevare se la cella è attiva o meno, quindi perché preoccuparsi ? Vale la pena preoccuparsi?


Ecco il mio go, in qualche modo è cresciuto da ciò che ha postato @Joe Frambach ma volevo le seguenti aggiunte:

  1. conservare i jXHR e i callback di errore / successo se sono stati forniti
  2. Rimbalzo delle richieste identiche (per url e opzioni corrispondono) mentre si attivano ancora i callback o jqXHR forniti per ogni chiamata
  3. Utilizzare AjaxSettings per semplificare la configurazione
  4. Non avere ajax non in batch a svuotare il batch, quelli dovrebbero essere processi separati IMO, ma in questo modo fornire un'opzione per forzare un batch batch.

Ad ogni modo, questo pollone dovrebbe essere fatto meglio come plugin separato piuttosto che scavalcare e influenzare la funzione predefinita .ajax ... divertiti:

(function($) {
    $.ajaxSetup({
        batchInterval: 5*60*1000,
        flushBatch: false,
        batchable: false,
        batchDebounce: true
    });

    var batchRun = 0;
    var batches = {};
    var oldAjax = $.fn.ajax;

    var queueBatch = function(url, options) {
        var match = false;
        var dfd = new $.Deferred();
        batches[url] = batches[url] || [];
        if(options.batchDebounce || $.ajaxSettings.batchDebounce) {

            if(!options.success && !options.error) {
                $.each(batches[url], function(index, batchedAjax) {
                    if($.param(batchedAjax.options) == $.param(options)) {
                        match = index;
                        return false;
                    }
                });
            }

            if(match === false) {
                batches[url].push({options:options, dfds:[dfd]});
            } else {
                batches[url][match].dfds.push(dfd);
            }

        } else {
            batches[url].push({options:options, dfds:[dfd]);
        }
        return dfd.promise();
    }

    var runBatches = function() {
        $.each(batches, function(url, batchedOptions) {
            $.each(batchedOptions, function(index, batchedAjax) {
                oldAjax.apply(null, url, batchedAjax.options).then(
                    function(data, textStatus, jqXHR) {
                        var args = arguments;
                        $.each(batchedAjax.dfds, function(index, dfd) {
                            dfd.resolve(args);
                        });
                    }, function(jqXHR, textStatus, errorThrown) {
                        var args = arguments;
                        $.each(batchedAjax.dfds, function(index, dfd) {
                            dfd.reject(args);
                        });
                    }
                )
            });
        });
        batches = {};
        batchRun = new Date.getTime();
    }

    setInterval(runBatches, $.ajaxSettings.batchInterval);

    $.fn.ajax = function(url, options) {
        if (options.batchable) {
            var xhr = queueBatch(url, options);
            if((new Date.getTime()) - batchRun >= options.batchInterval) {
                runBatches();
            }
            return xhr;
        }
        if (options.flushBatch) {
            runBatches();
        }
        return oldAjax.call(null, url, options);
    };
})(jQuery);

Ho fatto dei progressi nell'aggiungere l'opzione a $.ajax , $.ajax iniziato a modificare la domanda e ho capito che è meglio come risposta:

(function($) {
    var batches = [];
    var oldAjax = $.fn.ajax;
    var lastAjax = 0;
    var interval = 5*60*1000; // Should be between 2-5 minutes
    var runBatches = function() {
        var now = new Date().getTime();
        var batched;
        if (lastAjax + interval < now) {
            while (batched = batches.pop()) {
                oldAjax.apply(null, batched);
            }
        }
    }
    setInterval(runBatches, interval);
    $.fn.ajax = function(url, options) {
        if (options.batchable) {
            batches.push(arguments);
            return;
        }
        runBatches();
        oldAjax.apply(null, arguments);
        lastAjax = now;
    };
})(jQuery);

Questo era in realtà abbastanza semplice. È amore vedere una risposta migliore però.


L'articolo che hai collegato si concentra chiaramente sull'ottimizzazione del consumo energetico per le app (sì, l'esempio del widget meteo è terrificante). L'utilizzo attivo di un browser è, per definizione, un'attività in primo piano; inoltre qualcosa come ApplicationCache è già disponibile per ridurre la necessità di richieste di rete. È quindi possibile aggiornare la cache a livello di codice come richiesto ed evitare il fai-da-te.

Nota stonata scettica: se si utilizza jQuery come parte della propria app HTML5 (magari avvolto in Sencha o simile), forse la struttura dell'app mobile ha più a che fare con l'ottimizzazione delle richieste rispetto al codice stesso. Non ho alcuna prova, ma maledizione suona bene :)

  • Come posso rendere questa una vera modifica del solo metodo {batchable:true} , aggiungendo {batchable:true} al metodo? Non l'ho nemmeno capito.

Un approccio perfettamente valido, ma per me questo sembra un colpo di papera andato male. Non lo farei Anche se si batchable correttamente il batchable su falso, personalmente preferirei usare una facciata (forse anche nel proprio spazio dei nomi?)

var gQuery = {}; //gQuery = green jQuery, patent pending :)
gQuery.ajax = function(options,callback){
  //your own .ajax with blackjack and hooking timeouts, ultimately just calling
  $.ajax(options);
}
  • setInterval mantiene il telefono sempre attivo? È una brutta cosa da fare? C'è un modo migliore per non farlo?

Le implementazioni native di setInterval e setTimeout sono molto simili afaik; pensare a quest'ultimo non sparando mentre il sito web è in background per le richieste di inattività bancarie online; quando una pagina non è in primo piano, la sua esecuzione è sostanzialmente interrotta. Se un'API è disponibile per tali "differimenti" (le citazioni di articoli di alcune importanti funzionalità di iOS7), è probabile che sia un approccio preferibile, altrimenti non vedo alcun motivo per evitare setInterval .

  • Ci sono altre cose qui che farebbero scaricare la batteria più velocemente?

Immagino che qualsiasi carico pesante sarebbe (dal calcolo di pi alle transizioni 3d piuttosto forse). Ma mi sembra un'ottimizzazione prematura per me e mi ricorda un e-reader con modalità di risparmio energetico che ha completamente disattivato lo schermo LCD :)

  • Questo tipo di approccio vale anche la pena? Ci sono così tante cose contemporaneamente in uno smartphone moderno, che se la mia app non usa la cella, sicuramente un'altra app lo è. Javascript non può rilevare se la cella è attiva o meno, quindi perché preoccuparsi? Vale la pena preoccuparsi?

L'articolo ha sottolineato che un'app meteorologica è irragionevolmente avida e che mi preoccuperebbe. Sembra essere una svista dello sviluppo, ma più di ogni altra cosa, come nel recuperare i dati più spesso di quanto sia realmente necessario. In un mondo ideale, questo dovrebbe essere ben gestito a livello di sistema operativo, altrimenti si finirebbe con una serie di soluzioni alternative. IMO: non preoccuparti fino a quando l'alta scalabilità non pubblicherà un altro articolo che ti dice di :)





asynchronous