web-dev-qa-db-fra.com

Qu'advient-il de l'exécution de JavaScript (décompte, etc.) lorsque l'iPhone / Android se met en veille?

J'ai une application Web jQuery Mobile qui cible iOS et Android. Un composant de l'application est une tâche en arrière-plan, qui vérifie périodiquement a.) Les modifications des données locales et b.) La connectivité à le serveur. Si les deux sont vrais, la tâche pousse les modifications.

J'utilise une simple fonction basée sur setTimeout () pour exécuter cette tâche. Chaque condition d'échec ou de succès appelle setTimeout () sur la tâche d'arrière-plan, en s'assurant qu'elle s'exécute à des intervalles de 30 secondes. Je mets à jour une div d'état avec l'horodatage du dernier runtime de tâche à des fins de débogage.

Dans tout navigateur de bureau, cela fonctionne très bien; cependant, sur iOS ou Android, après un certain temps, la tâche cesse de s'exécuter. Je me demande si cela est lié aux paramètres d'économie d'énergie des appareils - lorsque iOS entre en veille, cela met-il fin à l'exécution de JavaScript? C'est ce qui semble se produire.

Si oui, quelle est la meilleure façon de reprendre? Y a-t-il un événement de réveil auquel je peux me connecter? Sinon, quelles sont les autres options qui n'impliquent pas de se connecter à des événements dépendant de l'interaction de l'utilisateur (je ne veux pas lier la page entière à un événement de clic juste pour redémarrer la tâche d'arrière-plan).

37
Christopher

Il semble que l'exécution de Javascript soit suspendue sur MobileSafari lorsque la page du navigateur n'est pas ciblée. Il semble également que si les événements setInterval () sont en retard, ils sont simplement déclenchés dès que le navigateur est focalisé. Cela signifie que nous devrions être en mesure de maintenir un setInterval () en cours d'exécution et de supposer que le navigateur a perdu/retrouvé le focus si la fonction setInterval a pris beaucoup plus de temps que d'habitude.

Ce code vous avertit après être revenu à partir d'un onglet de navigateur, après être revenu à partir d'une autre application et après avoir quitté le mode veille. Si vous définissez votre seuil un peu plus longtemps que votre setTimeout (), vous pouvez supposer que votre délai ne se terminera pas si cela se déclenche.

Si vous souhaitez rester prudent: vous pouvez enregistrer votre ID de délai d'expiration (renvoyé par setTimeout) et le définir sur un seuil plus court que votre délai d'expiration, puis réexécuter clearTimeout () et setTimeout () si cela se déclenche.

<script type="text/javascript">
var lastCheck = 0;

function sleepCheck() {
        var now = new Date().getTime();
        var diff = now - lastCheck;
        if (diff > 3000) {
                alert('took ' + diff + 'ms');
        }
        lastCheck = now;
}

window.onload = function() {
        lastCheck = new Date().getTime();
        setInterval(sleepCheck, 1000);
}
</script>

Modifier: Il semble que cela puisse parfois se déclencher plus d'une fois de suite lors de la reprise, vous devez donc gérer cela d'une manière ou d'une autre. (Après avoir laissé mon Android dormir toute la nuit, il a réveillé jusqu'à deux alertes). Je parie que Javascript a repris à un moment arbitraire avant de dormir complètement.)

J'ai testé sur Android 2.2 et le dernier iOS - ils alertent tous les deux dès que vous sortez du sommeil.

29
lunixbochs

Lorsque l'utilisateur passe à une autre application ou que l'écran se met en veille, les minuteries semblent s'arrêter jusqu'à ce que l'utilisateur revienne à l'application (ou lorsque l'écran se réveille).

Phonegap a un événement resume que vous pouvez écouter au lieu d'interroger l'état (ainsi qu'un événement pause si vous voulez faire des choses avant qu'il ne soit pas au point). Vous commencez à l'écouter après le déclenchement de deviceReady.

document.addEventListener("deviceready", function () {
    // do something when the app awakens
    document.addEventListener('resume', function () {
        // re-create a timer.
        // ...
    }, false);
}, false);

J'utilise angular avec phonegap et j'ai un service implémenté qui gère un certain délai d'attente pour moi, mais fondamentalement, vous pouvez créer un objet qui règle le minuteur, annule le minuteur et surtout, met à jour le minuteur ( mise à jour est ce qui est appelé lors de l'événement "reprendre").

Dans angular J'ai une portée et une portée racine auxquelles je peux attacher des données, mon délai d'expiration est global, donc je l'attache à la portée racine mais pour les besoins de cet exemple, je vais simplement l'attacher à l'objet document. Je ne pardonne pas cela parce que vous devez l'appliquer à une sorte de portée ou d'espace de noms.

var timeoutManager = function () {
    return {
        setTimer: function (expiresMsecs) {
            document.timerData = {
                timerId: setTimeout(function () {
                    timeoutCallback();
                },
                expiresMsecs),

                totalDurationMsecs: expiresMsecs,
                expirationDate: new Date(Date.now() += expiresMsecs)
            };
        },

        updateTimer: function () {
            if (document.timerData) {
                //
                //  Calculate the msecs remaining so it can be used to set a new timer.
                //
                var timerMsecs = document.timerData.expirationDate - new Date();

                //
                //  Kill the previous timer because a new one needs to be set or the callback
                //  needs to be fired.
                //
                this.cancelTimer();

                if (timerMsecs > 0) {
                    this.setTimer(timerMsecs);
                } else {
                    timeoutCallback();
                }
            }
        },

        cancelTimer: function () {
            if (document.timerData && document.timerData.timerId) {
                clearTimeout(document.timerData.timerId);
                document.timerData = null;
            }
        }
    };
};

Vous pouvez demander à la fonction de gestionnaire de prendre un paramètre en millisecondes au lieu de le passer dans set, mais là encore, cela est modélisé quelque peu après le service angular que j'ai écrit. Les opérations doivent être suffisamment claires et concises pour faire quelque chose avec eux et ajoutez-les à votre propre application.

var timeoutCallback = function () { console.log('timer fired!'); };
var manager = timeoutManager();
manager.setTimer(20000);

Vous souhaiterez mettre à jour la minuterie une fois que vous aurez l'événement de reprise dans votre écouteur d'événements, comme ceci:

// do something when the app awakens
document.addEventListener('resume', function () {
    var manager = timeoutManager();
    manager.updateTimer();
}, false);

Le gestionnaire de délai d'attente a également cancelTimer() qui peut être utilisé pour tuer le minuteur à tout moment.

4
Jeff LaFay

Vous pouvez utiliser cette classe github.com/mustafah/background-timer basé sur la réponse @jlafay, où vous pouvez utiliser comme suit:

coffeescript

timer = new BackgroundTimer 10 * 1000, ->
    # This callback will be called after 10 seconds
    console.log 'finished'

timer.enableTicking 1000, (remaining) ->
    # This callback will get called every second (1000 millisecond) till the timer ends
    console.log remaining

timer.start()

javascript

timer = new BackgroundTimer(10 * 1000, function() {
    // This callback will be called after 10 seconds
    console.log("finished");
});

timer.enableTicking(1000, function(remaining) {
    // This callback will get called every second (1000 millisecond) till the timer ends
    console.log(remaining);
});

timer.start();

J'espère que ça aide, merci ...

2
Mustafah

Vous devez utiliser Page Visibility API (MDN) qui est pris en charge un peu partout. Il peut détecter si une page ou un onglet est redevenu visible et vous pouvez ensuite reprendre vos délais d'attente ou effectuer certaines actions.

0
sanjsanj