Thursday, July 02, 2009

Gmail for Mobile HTML5 Series: Using Timers Effectively

On April 7th, Google launched a new version of Gmail for mobile for iPhone and Android-powered devices. We shared the behind-the-scenes story through this blog and decided to share more of what we've learned in a brief series of follow-up blog posts. This week, I'll talk about two kinds of timers Javascript provides and how you can put them to good use in your own web applications.

Javascript Timer API

Timers provide a way to schedule a function to run at some later point in time. The two functions used to create timers are:
  • var id = setTimeout(fn, delay) creates a timer which calls the specified function once after the given delay.
  • var id = setInterval(fn, delay) is similar, but the function is called continually until the timer is canceled.
Each type of timer has a corresponding clear method (e.g., clearTimeout(id)) that stops a timer while it is running.

There are many great resources online already that document the nitty-gritty details of this API. That's not our focus here. Instead, I want to talk about specific ways in which you can put these timers to work for you in your web application.

Let's Do the Time Warp Again

It's important to keep in mind that just because you ask for a timer with a certain delay doesn't mean it will fire precisely that many milliseconds later. On current browsers, all Javascript code executes within a single thread. This means all your timers have to contend for execution time not only with each other, but also with all the other Javascript code in your application. If another block of code is in the middle of executing when a timer should fire, the timer will be delayed until that block of code is done. (Help is on the way: HTML5's Web Workers will allow web applications to spawn workers that run scripts in parallel.)

Let's look at a concrete example of what this means in practice. I added a timer to Gmail for mobile that is supposed to fire every 200 ms. Each time it fires, it records the time between consecutive timer ticks. Here are the results after 100 ticks:


The dashed blue line represents the requested timer interval, 200 ms. Notice that the median was almost 50% higher than requested, at 276 ms; the time between ticks varied from 98 ms to almost 4 seconds, with an average delay of 493 ms.

This highlights the fact that the interval at which your timer actually fires may differ greatly from the requested delay. In fact, the time between ticks is typically highly variable. It will fluctuate based on what else is happening in your application and on the device. Don't rely on your timer being precise!

Buy One Timer, Get One Free?

When I first started working on the new version of Gmail for mobile, the application used only a couple of timers. As we continued adding more and more features, the number of timers grew. We were curious about the performance implications: would 10 concurrent timers make the app feel slow? How about 100? How would the performance of many low-frequency timers compare to a single high-frequency timer?

To answer these questions, we conducted a few experiments. We injected some extra code into a development build of Gmail that created a lot of different timers, each of which just performed some simple calculations. Then we fired up the app on an iPhone 3G and Android G1 (both running the latest version of their respective firmware) and observed the performance. Note that although we could have just created a separate test page for this, we chose to inject the code right into our app so we could see how fast it would be to read and process mail with all those timers running.

Here's what we learned. With low-frequency timers — timers with a delay of one second or more — we could create many timers without significantly degrading performance on either device. Even with 100 timers scheduled, our app was not noticeably less responsive. With high-frequency timers, however, the story was exactly the opposite. A few timers firing every 100-200 ms was sufficient to make our UI feel sluggish.

This led us to take a mixed approach with the timers we use in our application. For timers that have a reasonably long delay, we just freely create new timers wherever they are needed. However, for timers that need to execute many times each second, we consolidate all of the work into a single global high-frequency timer.

One High-Frequency Timer to Rule Them All

A global high-frequency timer strikes a balance between needing several different functions to execute frequently and application performance. The idea is simple: create a single timer in a central class that calls as many functions as required. Ours looks something like this:


var highFrequencyTimerId_ = window.setInterval(globalTimerCallback, 100);

globalTimerCallback = function() {
navigationManager.checkHash();
spinnerManager.spin();
detectWakeFromSleep_();
};

Why did we choose to hardcode the various function calls in globalTimerCallback() rather than implementing a more generic callback registration interface? Keep in mind that this code is going to execute many times every second. Looping over an array of registered callbacks might be slightly "cleaner" code, but it's critical that this function execute as quickly as possible. Hardcoding the function calls also makes it really easy to keep track of all the work that is being done within the timer.

To Die, To Sleep; To Sleep, Perchance to Tick

One neat application of a high-frequency timer is to detect when the device has been woken from sleep. When your application is put to sleep — either because the device is put into sleep mode or because the user has navigated away from the browser to another application — time, as perceived by your app, pauses. No timers fire until the user navigates back to your app and it wakes up. By keeping a close eye on the actual time that elapses between high-frequency timer ticks, you can detect when the app wakes from sleep.

First, create a high-frequency timer, as described above. In your timer, call a function that keeps track of the time between ticks:


// The time, in ms, that must be "missed" before we
// assume the app was put to sleep.
var THRESHOLD = 10000;

var lastTick_;
detectWakeFromSleep_ = function() {
var now = new Date().getTime();
var delta = now - this.lastTick_;
if (delta > THRESHOLD) {
// The app probably just woke up after being asleep.
fetchUpdatedData();
}
lastTick_ = now;
};

Now your users will always have the latest data available when they return to your app without having to manually perform a refresh!

I hope this helps you make the most of timers in your web application. Stay tuned for the next post, where we'll discuss some cool tricks to spice up the text areas on your forms.

9 comments:

  1. Really intereresting post. What did you find to be best threshold time to be for testing for waking from sleep?

    ReplyDelete
  2. I didn't know this before reading this article. So if you move away from Safari on the iPhone and then launch Safari again, your Javascript thread is actually frozen and then resumed from where it left off?

    ReplyDelete
  3. Great questions!

    Alex, I've found 10 seconds is a fairly safe threshold for most situations. The best threshold time will vary from app to app, though, depending on the Javascript workload. I'd suggest trying out the experiment I described with your own app: add a high-frequency timer using setInterval() that logs the time between ticks and then check out the results.

    Harish, that's correct. For example, suppose you call setTimeout(myFunction, 10000), wait five seconds, then close Mobile Safari. When you come back to Mobile Safari, myFunction will be invoked after five seconds (for a total of ten seconds).

    There's one caveat here: if there is memory pressure from other applications on the device -- for example, if you spend some time in other apps before coming back to Mobile Safari -- you'll find that the entire app reloads when you return, which will kill any timers that were pending.

    Hope this helps!

    ReplyDelete
  4. Dear sir,

    I don't know and now i must can be doing , i was confirmation my key ApI

    (ABQIAAAAObfNukxTEA7bl042y-lirxTJwjmsFvhv9SvHn1uXRk8Sg6nctBTTYMv7wVIRMmRIjeSvKS3RFA845A)

    Please Help me by email instucture to me

    After this end confirmation Url and Key API and be conutinous....what will be me doing....?


    Thanks/ wiwied

    email : info_wow35@yahoo.com

    ReplyDelete
  5. Dear sir,

    With this I say that I registered to become a member of the API, the API code for me less so because I understand mendaftara another account with the name of the

    (wida21yati@gmail.com) by using the url = http://ketawablog.blogspot.com, while I register for Open Source blog 13 hours, using the email registration info_wow35@yahoo.com, but now I make sure my email admin1507 @ gmail.com and wiwiedss@gmail.com


    I open the connection with my Blogger to Publish in Because I had difficulty in making the registration form, for the Prospect to the front of my client experience obstacles,
    I was happy because bloggers use links to post and upload widget is provided.

    akan but Blogger does not provide for the registration form Customernya ...... so we can not do for apa2 that does not understand much about the database.

    Please help me because I do not correct such as what the next procedure, denagn condition of my english is very limited,

    please give me a guide by email STEP 2 what do I need to do for the next process.

    Thanks / wiwied

    ReplyDelete
  6. Sigh...I wish my Palm Treo could use this..

    ReplyDelete
  7. Guys, the post is interesting but don't you think is a bit embarassing to have scam in your comments? Regards

    ReplyDelete
  8. Really interesting article - I think the bit about hard coding the function calls in the callback could be a little clearer - probably just by replacing the reference to onTick_() with a reference to globalTimerCallback which is used in the example code. I had to read that a couple of times to make sense!

    ReplyDelete
  9. Hi Tom,

    Oops! You're right -- this should say globalTimerCallback(). Thanks for pointing this out, and sorry for the confusion!

    ReplyDelete