22

最近、この記事が HackerNews のトップを飾った: http://highscalability.com/blog/2013/9/18/if-youre-programming-a-cell-phone-like-a-server-youre-doing.html#

その中で次のように述べています。

セル無線は、電話で最大のバッテリー消耗の 1 つです。データを送信するたびに、どんなに小さなデータでも、無線の電源が 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 はセルがオンかどうかを検出できないのに、どうしてわざわざ? 気にする価値はありますか?

4

6 に答える 6

7

へのオプションの追加である程度の進歩を遂げ$.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);

それは実際にはかなり簡単でした。しかし、より良い答えを見るのが大好きです。

于 2013-09-20T05:26:44.333 に答える
5
  • setInterval は、電話を常に起動状態に保ちますか? それは悪いことですか?そうしないためのより良い方法はありますか?

iPhone 4、iOS 6.1.0 Safari 環境から:

A は、要素のテキストを 1 秒間隔で更新するカウントダウン タイマーを備えたアプリを作成しました。DOM ツリーの複雑さは中くらいでした。このアプリは、AJAX を一切使用しない比較的単純な電卓でした。しかし、私は常に、これらの 1 秒に 1 回のリフローが私を殺しているのではないかとこっそり疑っていました。アプリのウェブページで Safari を使用して、テーブルの上に電源を入れたままにしておくと、バッテリーがかなり早く消耗するように見えました。

そして、そのアプリには 2 つのタイムアウトしかありませんでした。現在、タイムアウトがバッテリーを消耗させているという定量的な証拠はありませんが、このばかげた計算機から 45 分ごとに約 10% を失うことは少し不安でした. (しかし、それはバックライトだったのかもしれません。)

その点について:間隔で AJAX を実行したり、間隔で他のことを実行したりするテスト アプリを作成し、同様の条件下で各関数がどのようにバッテリーを消耗するかを比較することをお勧めします。制御された環境を実現するのは難しいかもしれませんが、ドレインに十分な差がある場合は、「不完全な」テスト条件でも、結論を出すのに十分な結果が得られます.


しかし、iOS 6.1.0 Safari がタイムアウトを処理する方法について興味深いことがわかりました。

  • 画面をオフにすると、タイムアウトはコールバックを実行しません。
  • 結果として、長時間のタイムアウトは「目標を達成できません」。

私のアプリのタイマーが正しい時間を表示することになっていた場合 (画面を閉じて再度開いた後でも)、簡単な方法でsecondsLeft -= 1. 画面をオフにすると、secondsLeft(開始時間に対して) は「遅れ」、したがって不正確になります。(画面がオフになっている間、setTimeout コールバックは実行されませんでした。)

timeLeft = fortyMinutes - (new Date().getTime() - startTime)解決策は、間隔ごとに再計算する必要があったことです。

また、私のアプリのタイマーは、有効期限が近づくにつれて、緑からライム、黄色、赤に変わるはずでした。この時点で、インターバルコードの効率が心配だったので、適切な時間に色の変更を「スケジュール」する方がよいのではないかと考えました (ライム: 開始時間の 20 分後、黄色: 30 分、赤: 35) (これは、99% の確率で無駄になるすべての間隔での 4 つの不等式チェックよりも好ましいように思われました)。

ただし、そのような色の変更をスケジュールし、目標の時間に携帯電話の画面をオフにした場合、その色の変更は発生しません。

解決策は、最後の 1 秒のタイマー更新からの経過時間が「>= 2秒」であるかどうかを各間隔で確認することでした。(このようにして、アプリは私の電話の画面がオフになっていたかどうかを知ることができました。「遅れた」ことを認識することができました。)その時点で、必要に応じて、色の変更とスケジュールを「強制的に」適用しました。次のもの。

(言うまでもなく、後でカラーチェンジャーを削除しました...)

だから、これは私の主張を確認すると信じています

画面がオフになっている場合、iOS 6.1.0 Safari は setTimeout コールバック関数を実行しません。

したがって、AJAX 呼び出しを「スケジュール」するときは、このことを念頭に置いてください。おそらく、この動作にも影響を受けるからです。

そして、私の提案を使用して、あなたの質問に答えることができます。

  • 少なくとも iOS では、画面がオフのときに setTimeout がスリープすることがわかっています。
  • したがって、setTimeout はあなたの携帯電話に「悪夢」を与えません (「起きておいてください」)。

  • この種のアプローチは価値がありますか? 最近のスマートフォンでは非常に多くのことが同時に行われているため、私のアプリがセルを使用していない場合でも、別のアプリがセルを使用していることは間違いありません。Javascript はセルがオンになっているかどうかを検出できないのに、なぜ気にする必要があるのでしょうか? 気にする価値はありますか?

この実装を正しく機能させることができれば、価値があるように思えます

AJAX リクエストを行うたびにレイテンシが発生し、アプリの速度がある程度低下します。(結局のところ、レイテンシはページ読み込み時間の悩みの種です。) したがって、リクエストを「バンドル」することで、確実にいくらかの利益を得ることができます。リクエストを「バッチ処理」できるように $.ajax を拡張すると、間違いなくメリットがあります。

于 2013-09-30T02:24:22.090 に答える
3

あなたがリンクした記事は、アプリの消費電力の最適化に明確に焦点を当てています(そうです、天気ウィジェットの例は恐ろしいものです)。ブラウザを積極的に使用することは、定義上、フォアグラウンド タスクです。さらに、ネットワーク要求の必要性を減らすために、ApplicationCacheのようなものが既に利用可能です。その後、必要に応じてプログラムでキャッシュを更新し、DIY を回避できます。

懐疑的な補足: HTML5 アプリの一部として jQuery を使用している場合 (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は非常によく似ています。Web サイトがオンライン バンキングの非アクティブ プロンプトのバックグラウンドにある間は、後者は起動しないと考えてください。ページがフォアグラウンドにない場合、その実行は基本的に停止します。API がそのような「延期」に使用できる場合 (関連する iOS7 の機能について言及している記事)、それはおそらく好ましいアプローチsetIntervalです。

  • バッテリーの消耗を早める原因となるものは他にありますか?

重い負荷がかかると推測します(piおそらく計算からかなりの3Dトランジションまで)。しかし、これは私には時期尚早の最適化のように聞こえ、LCD 画面を完全にオフにするバッテリー節約モードを備えた電子書籍リーダーを思い出させます :)

  • この種のアプローチは価値がありますか? 最近のスマートフォンでは非常に多くのことが同時に行われているため、私のアプリがセルを使用していない場合でも、別のアプリがセルを使用していることは間違いありません。Javascript はセルがオンになっているかどうかを検出できないのに、なぜ気にする必要があるのでしょうか? 気にする価値はありますか?

この記事では、天気アプリが不当に貪欲であると指摘しており、それは私にとって懸念事項です。本当に必要以上に頻繁にデータを取得するなど、何よりも開発の見落としのようです。理想的な世界では、これは OS レベルで適切に処理する必要があります。そうしないと、一連の競合する回避策が必要になります。IMO:高スケーラビリティが別の記事を投稿するまで気にしないでください:)

于 2013-09-30T00:48:49.057 に答える
1

これが私のバージョンです:

(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() を呼び出すことでトリガーできます
于 2013-09-26T19:40:46.640 に答える
1

$.ajaxメソッドをハッキングする限り、私は次のことを行います。

  • Promiseによって提供されるメカニズムも保持しようとします$.ajax
  • グローバル ajax イベントの 1 つを利用して 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分?)を置くことも、完全に削除することもできます-それはすべて実際にあなたのアプリに依存します.

フィドル


一般的な質問「5 分で携帯電話のバッテリーを消費しない Web アプリを作成するにはどうすればよいですか?」について : その 1 つに対処するには、複数の魔法の矢が必要です。これは、あなたがしなければならない一連の設計上の決定であり、実際にはあなたのアプリに依存します.

一度にできるだけ多くのデータをロードする (そしておそらく使用されないデータを送信する) か、必要なものを取得する (そして多くの小さな個別の要求を送信する) かを調整する必要があります。

考慮すべきいくつかのパラメータは次のとおりです。

  • データの量 (クライアントのデータ プランも枯渇させたくありません...)、
  • サーバー負荷、
  • どれだけキャッシュできるか、
  • 「最新」であることの重要性 (チャット アプリの 5 分遅れは機能しません)、
  • クライアントの更新の頻度 (ネットワーク ゲームでは、クライアントからの多くの更新が必要になる可能性がありますが、ニュース アプリではおそらくそれよりも少ない...)。

1 つのかなり一般的な提案:「ライブ更新」チェックボックスを追加し、その状態をクライアント側に保存できます。チェックを外すと、クライアントは「更新」ボタンを押して新しいデータをダウンロードする必要があります。

于 2013-09-27T13:12:20.753 に答える
0

これが私のやり方です。@Joe Frambachが投稿したものからいくらか成長しましたが、次の追加が必要でした:

  1. jXHR およびエラー/成功のコールバックが提供されている場合は保持する
  2. 各呼び出しに提供されたコールバックまたは jqXHR を引き続きトリガーしながら、(URL とオプションの一致によって) 同一の要求をデバウンスします。
  3. AjaxSettings を使用して構成を簡単にする
  4. バッチ化されていない 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);
于 2013-09-30T22:04:15.037 に答える