I think that Android is a platform created by developers for developers. That’s why sometimes it offers several different ways to do the same thing.
Let’s say you want to execute some periodic task in background, even if currently none of your Activities is active. How you can achieve this?
First of all, you should be careful with Application process lifecycle. Basically, if none of your activities are visible, Android will destroy your app at some point in the future. To avoid this, you can create your Service and span a background thread in it. A tricky thing about a service is that it is not a separate thread or process from your app, it exists in the same context. That’s why if you want to do some long work you need to spawn another thread or use AsyncTask.
Ok, let’s say we created our Service for app and now we want to execute some periodic task. One easy way to do this is to use Handlers. You can think of a Handler as of a background thread that is waiting for Runnables to be posted to it. A Handler also can receive Messages
To create a new Handler on separate thread you need to do this:
How do we execute a periodic task using this handler? Easily. We need to post a runnable at some time which will reschedule self execution:
This example code is very simple and you can find it anywhere in the Internet. But there is one caveat in it. postDelayed method does not count time which device spends in deep sleep. Which means that if you schedule a callback runnable to be executed after 5 seconds from now, and then the device goes to deep sleep (e.g. user locks the device) your runnable will not be executed while device is in sleep. It will be executed when time spent before sleep and after sleep is equal to five seconds.
This can be a little misleading, since postDelayed documentation doesn’t mention it. Documentation says:
Causes the Runnable r to be added to the message queue, to be run after the specified amount of time elapses. The runnable will be run on the thread to which this handler is attached.
Nothing about “time spent in deep sleep is not counted”. But if you look at other methods of Handler thread, you will get an idea of what is happening under the hood. There is a method called postAtTime which schedules to execute runnable at some time in future. And time base is SystemClock.uptimeMillis() which is a specific android timer which represents: “milliseconds since boot, not counting time spent in deep sleep. Note: This value may get reset occasionally (before it would otherwise wrap around)”.
Aha! Here is the first piece of information about the “time spent in deep sleep”. May be postDelayed is just a wrapper for postAtTime? Android is an open source project, so we can easily check our guess by inspecting the source code. Yes, actually calling postDelayed actually ends up executing this code in Handler class:
That is the reason why postDelayed is not counting time when screen is blanked out! I think the documentation is a little bit misleading at this point.
I am smiling right now. Why? Because while I am writing this post, I decided to report this bug in the doc. But before doing report I searched fo existing and found one. Somebody reported it 21 hours ago!
Ok now we understand the behaviour of postDelayed. But what if we still want to execute some code even if device is in deep sleep. Is there any way to do this?
Yes! You need to use AlaramManager. Alarm manager gives you a way to schedule sending of Intent some point in the future. Intent can be sent to Activity or BroadcastReceiver. But you should be careful with the arguments you use to schedule an alarm. If you want it to be executed, you should use ELAPSED_REALTIME_WAKEUP alarm type and use SystemClock.elapedRealtime() method and add your desired delay. But keep in mind that using AlarmManager is more expensive then using Handlers and postDelayed.
Let’s summarise what we have learned today:
- If you need to execute some periodic task in background you can use a separate Handler to do that
- If it is critical for your app to execute some task in background at some point in future, even if device is in sleep you should use AlarmManager with type set to AlarmManager.ELAPSED_REALTIME_WAKEUP
- You should avoid using AlarmManager unless you really need it. postDelayed() on handlers is cheaper.