Подтвердить что ты не робот

Запросы дозирования для минимизации слива

Эта статья недавно попала в верхнюю часть HackerNews: http://highscalability.com/blog/2013/9/18/if-youre-programming-a-cell-phone-like-a-server-youre-doing.html#

В котором говорится:

Ячейка радио является одним из самых больших разрядов батареи на телефоне. Каждый раз, когда вы отправляете данные, независимо от того, насколько они малы, радиоприемник работает на 20-30 секунд. Каждое принятое вами решение должно основываться на минимизации количества раз, когда радио включается. Время автономной работы можно значительно улучшить, изменив способ передачи данных приложениям. Теперь пользователи хотят получить свои данные, трюк - это балансировка пользовательского интерфейса при передаче данных и минимизация использования энергии. Баланс достигается за счет того, что приложения тщательно связывают все повторяющиеся и прерывистые передачи вместе, а затем агрессивно предваряют прерывистые передачи.

Я хотел бы изменить $.ajax, чтобы добавить такой параметр, как "не нужно делать прямо сейчас, просто выполните этот запрос при запуске другого запроса". Что было бы хорошим способом сделать это?

Я начал с этого:

(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);

Я не могу сказать по формулировке статьи, я думаю, что хороший пакетный интервал составляет 2-5 минут, поэтому я просто использовал 5.

Является ли это хорошей реализацией?

  • Как я могу сделать это истинным изменением только метода ajax, добавив параметр {batchable:true} к методу? Я не совсем понял это.

  • Поддерживает ли телефон setInterval все время? Это плохо? Есть ли лучший способ не делать этого?

  • Есть ли здесь другие вещи, которые могли бы привести к тому, что батарея разрядится быстрее?

  • Является ли такой подход даже стоящим? В современном смартфоне сразу происходит так много вещей, что если мое приложение не использует камеру, наверняка какое-то другое приложение. Javascript не может определить, включена ли ячейка или нет, так зачем беспокоиться? Стоит ли беспокоиться?

4b9b3361

Ответ 1

В статье, на которой вы четко указали, основное внимание уделяется оптимизации потребления энергии для приложений (да, пример погодного виджета ужасен). Активное использование браузера - по определению задача переднего плана; плюс что-то вроде ApplicationCache уже доступно для снижения потребности в сетевых запросах. Вы можете программно обновлять кеш по мере необходимости и избегать DIY.

Скептическая сторона примечания: если вы используете jQuery как часть вашего приложения HTML5 (возможно, завернутый в Sencha или аналогичный), возможно, инфраструктура мобильного приложения больше связана с оптимизацией запросов, чем сам код. У меня нет никаких доказательств, но, черт возьми, это звучит правильно:)

  • Как я могу сделать это настоящей модификацией только метода ajax, путем добавление опции {batchable:true} к методу? Я не совсем тоже понял.

Совершенно правильный подход, но для меня это звучит как утиная перфорация пошла не так. Я бы не стал. Даже если вы правильно по умолчанию batchable присвоили false, лично я предпочел бы использовать фасад (возможно, даже в своем собственном пространстве имен?)

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 все время? Это плохой вещь делать? Есть ли лучший способ не делать этого?

Нативные реализации setInterval и setTimeout очень похожи на afaik; подумайте о том, что последние не стреляют, а веб-сайт находится в фоновом режиме для подсказок о бездействии онлайн-банкинга; когда страница не находится на переднем плане, ее выполнение в основном остановлено. Если API доступен для таких "отсрочек" (в статье упоминаются некоторые релевантные возможности iOS7), то это, вероятно, предпочтительный подход, иначе я не вижу причин избегать setInterval.

  • Существуют ли другие вещи, которые могут привести к утечке батареи быстрее?

Я бы предположил, что любая тяжелая нагрузка (от вычисления pi до довольно 3D-переходов, возможно). Но это звучит как преждевременная оптимизация для меня и напоминает мне об электронном читателе с режимом экономии заряда батареи, который полностью выключил ЖК-экран:)

  • Возможно ли такой подход? Есть так много вещей сразу же в современном смартфоне, что если мое приложение не использует ячейка, конечно, какое-то другое приложение. Javascript не может определить, ячейка включена или нет, так зачем беспокоиться? Стоит ли беспокоиться?

В статье указывалось, что приложение для погоды является необоснованно жадным, и это касается меня. Похоже, что это надзор за развитием, хотя и больше, чем что-либо другое, например, при сборе данных чаще, чем это действительно необходимо. В идеальном мире это должно быть хорошо обработано на уровне ОС, иначе у вас будет множество конкурирующих обходных решений. ИМО: не утруждайтесь, пока highscalability не опубликует еще одну статью, говорящую вам:)

Ответ 2

Я сделал некоторый прогресс в добавлении опции в $.ajax, начал редактировать вопрос и понял это как ответ:

(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);

Это было довольно просто. Есть ли любовь видеть лучший ответ, хотя.

Ответ 3

  • Поддерживает ли setInterval все время пробуждения телефона? Это плохо? Есть ли лучший способ не делать этого?

Из iPhone 4, среда iOS 6.1.0 Safari:

A написал приложение с таймером обратного отсчета, который обновил текст элемента с интервалом в одну секунду. Дерево DOM имело среднюю сложность. Приложение было относительно простым калькулятором, который не делал никаких AJAX. Однако у меня всегда было подозрительное подозрение, что эти выплаты раз в секунду меня убивали. Моя батарея наверняка истощалась довольно быстро, всякий раз, когда я оставлял ее включенной на столе, с Safari на веб-странице приложения.

И в этом приложении было всего два тайм-аута. Теперь у меня нет никаких поддающихся количественной оценке доказательств того, что тайм-ауты истощают мою батарею, но потерять около 10% каждые 45 минут от этого допингового калькулятора немного расстроило. (Кто знает, возможно, это была подсветка.)

В этой заметке: Возможно, вам захочется создать тестовое приложение, которое делает AJAX по интервалам, другим вещам с интервалами и т.д., и сравнить, как каждая функция истощает батарею в аналогичных условиях. Получение контролируемой среды может быть сложным, но если есть достаточно большая разница в утечке, то даже "несовершенные" условия тестирования приведут к достаточно заметным результатам для вас сделать вывод.


Однако я узнал, что iOS 6.1.0 Safari обрабатывает таймауты:

  • Таймауты не запускают обратные вызовы, если вы выключаете экран.
  • В результате долгосрочные тайм-ауты будут "пропустить отметку".

Если таймер моего приложения должен был отображать правильное время (даже после того, как я закрыл и снова открыл экран), тогда я не мог идти по легкому маршруту и ​​делать secondsLeft -= 1. Если бы я выключил экран, то secondsLeft (относительно моего начального времени) было бы "позади" и, следовательно, неверным. (Обратный вызов setTimeout не запускался во время выключения экрана.)

Решение заключалось в том, что мне приходилось пересчитывать timeLeft = fortyMinutes - (new Date().getTime() - startTime) на каждом интервале.

Кроме того, таймер в моем приложении должен был меняться от зеленого, до лайма, до желтого, красного, по мере приближения к истечению срока. Поскольку в этот момент меня беспокоило эффективность моего интервального кода, я подозревал, что было бы лучше "запланировать" мои изменения цвета в течение их соответствующего времени (известь: через 20 минут после начала, желтый: 30 минут, красный: 35) (это казалось предпочтительным для проверки в четыре раза-неравенства на каждом интервале, что было бы бесполезно в 99% случаев).

Однако, если я планировал такое изменение цвета, и экран телефона был отключен в назначенное время, тогда такое изменение цвета никогда не произойдет.

Решение заключалось в проверке на каждом интервале, если время, прошедшее с момента последнего 1-секундного обновления таймера, было ">= 2 секунды". (Таким образом, приложение могло узнать, выключен ли экран моего телефона, он смог понять, когда он "упал".) В этот момент, при необходимости, я "насильственно" применил бы изменение цвета и расписание следующий.

(Излишне говорить, что я позже удалил устройство смены цвета...)

Итак, я считаю, что это подтверждает мое утверждение о том, что

iOS 6.1.0 Safari не выполняет функции обратного вызова setTimeout, если экран выключен.

Так что имейте это в виду, когда "планируете" ваши вызовы AJAX, потому что вы, вероятно, тоже будете затронуты этим поведением.

И, используя мое предложение, я могу ответить на ваш вопрос:

  • Как минимум для iOS, мы знаем, что setTimeout засыпает, пока экран выключен.
  • Таким образом, setTimeout не даст вашему телефону "кошмары" ( "не спать" ).

  • Возможно ли такой подход? В современном смартфоне сразу происходит так много вещей, что если мое приложение не использует камеру, наверняка какое-то другое приложение. Javascript не может определить, включена ли ячейка или нет, так зачем беспокоиться? Стоит ли беспокоиться?

Если вы можете заставить эту реализацию работать правильно, то кажется, что это было бы полезно.

Потребуется латентность для каждого запроса AJAX, который вы в какой-то степени замедлите свое приложение. (Задержка - это ошибка времени загрузки страницы, в конце концов.) Таким образом, вы наверняка достигнете некоторого выигрыша путем "связывания" запросов. Расширение $.ajax, так что вы можете "пакетные" запросы, безусловно, имеет некоторые достоинства.

Ответ 4

Вот моя версия:

(function($) {
    var batches = [],
        ajax = $.fn.ajax,
        interval =  5*60*1000, // Should be between 2-5 minutes
        timeout = setTimeout($.fn.ajax, interval);

    $.fn.ajax=function(url, options) {
        var batched, returns;
        if(typeof url === "string") {
            batches.push(arguments);
            if(options.batchable) {
                return;
            }
        }
        while (batched = batches.shift()) {
            returns = ajax.apply(null, batched);
        }
        clearTimeout(timeout);
        timeout = setTimeout($.fn.ajax, interval);
        return returns;
    }
})(jQuery);

Я думаю, что эта версия имеет следующие основные преимущества:

  • Если существует не-пакетный вызов ajax, соединение используется для отправки всех партий. Сброс таймера.
  • Возвращает ожидаемое возвращаемое значение при прямых вызовах ajax
  • Прямая обработка пакетов может быть вызвана вызовом $.fn.ajax() без параметров

Ответ 5

Что касается взлома метода $.ajax, я бы:

  • попытайтесь также сохранить механизм Promise, предоставленный $.ajax,
  • воспользоваться одним из глобальных событий ajax для запуска вызовов ajax,
  • может быть добавлен таймер, чтобы вызывать пакет в любом случае в случае, если не выполняется "немедленный" $.ajax вызов,
  • введите новое имя этой функции (в моем коде: $.batchAjax) и сохраните оригинал $.ajax.

Вот мой ход:

(function ($) {
    var queue = [],
        timerID = 0;

    function ajaxQueue(url, settings) {
    // cutom deferred used to forward the $.ajax' promise
        var dfd = new $.Deferred();

        // when called, this function executes the $.ajax call
        function call() {
            $.ajax(url, settings)
            .done(function () {
                dfd.resolveWith(this, arguments);
            })
            .fail(function () {
                dfd.rejectWith(this, arguments);
            });
        }

        // set a global timer, which will trigger the dequeuing in case no ajax call is ever made ...
        if (timerID === 0) {
            timerID = window.setTimeout(ajaxCallOne, 5000);
        }

        // enqueue this function, for later use
        queue.push(call);
        // return the promise
        return dfd.promise();
    }

    function ajaxCallOne() {
        window.clearTimeout(timerID);
        timerID = 0;

        if (queue.length > 0) {
            f = queue.pop();
        // async call : wait for the current ajax events
        //to be processed before triggering a new one ...
            setTimeout(f, 0);
        }
    }

    // use the two functions :

    $(document).bind('ajaxSend', ajaxCallOne);
    // or : 
    //$(document).bind('ajaxComplete', ajaxCallOne);

    $.batchAjax = ajaxQueue;
}(jQuery));

В этом примере жестко запрограммированная задержка в течение 5 секунд поражает цель "если менее 20 секунд между вызовами, она истощает батарею". Вы можете поместить большую (5 минут?) Или удалить ее вообще - все это зависит от вашего приложения.

fiddle


Относительно общего вопроса "Как написать веб-приложение, которое не сжигает батарею телефона через 5 минут?": для борьбы с этим потребуется больше одной магической стрелки. Это целый набор дизайнерских решений, которые вам придется принять, что действительно зависит от вашего приложения.

Вам нужно будет провести арбитражное разбирательство между загрузкой как можно большего количества данных за один раз (и, возможно, отправить данные, которые не будут использоваться), вместо того, чтобы получать то, что вам нужно (и, возможно, отправлять много небольших индивидуальных запросов).

Некоторые параметры, которые необходимо учитывать:

  • объем данных (вы не хотите сбрасывать план данных своих клиентов либо...),
  • загрузка сервера,
  • сколько можно кэшировать,
  • важность "обновления" (5 минут задержки для приложения чата не будут работать),
  • частота клиентских обновлений (сетевая игра, вероятно, потребует много обновлений от клиента, новостное приложение, вероятно, меньше...).

Одно довольно общее предложение: вы можете добавить флажок "live update" и сохранить его клиентскую часть состояния. Если флажок снят, клиент должен нажать кнопку "Обновить", чтобы загрузить новые данные.

Ответ 6

Вот мой ход, он несколько вырос из того, что отправил @Joe Frambach, но мне нужны следующие дополнения:

  • сохранить jXHR и обратные вызовы ошибок/успехов, если они были предоставлены
  • Отказывать идентичные запросы (по совпадению URL-адресов и опций), все еще вызывая обратные вызовы или jqXHR, предоставленные для КАЖДОГО вызова
  • Используйте AjaxSettings, чтобы упростить настройку
  • Не нужно, чтобы каждый неархивированный ajax запустил пакет, это должны быть отдельные процессы IMO, но, таким образом, предоставить возможность принудительно запускать пакетный поток.

В любом случае, эта присоска будет, скорее всего, лучше сделана как отдельный плагин, а не переопределит и повлияет на функцию по умолчанию .ajax... наслаждайтесь:

(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);