Leveraging Background Services and Agents in Windows Phone 7 (Mango)
Earlier this month at DevConnections, I gave a presentation on Background Services and Agents in Windows Phone 7. The next week, I got to join Jesse Liberty on his podcast interviewing Peter Torr, a Prinicpal Product Manager on the Windows Phone team who's written a lot of great content about this exact subject.
Overall, I was very impressed. I've done some basic Windows Phone development in the past, but nothing that really cutting edge. I really liked the way Windows Phone balanced enabling features for developers with protecting the phone's resources.
Side note: why you, Jon?
If you follow my blog, you might wonder what I was doing speaking on Windows Phone, since I don't talk about it much here. If you don't care, skip right on by to the next section...
I'd originally proposed this talk because I thought it looked fun. Later I passed it to Jesse Liberty since he works with Windows Phone all the time, but helped him write up the outline and sample code because I thought it looked like fun (and partly because I'd dumped the talk on him). Then when he found out the week before DevConnections that he'd be unable to attend, I requested to take the talk back, because it looked like fun. This stuff is just plain cool to work with, and I had a blast preparing and presenting on it.
While I don't do a lot of Windows Phone development, I've spent some time pair programming with Jesse on The Full Stack project, which includes a Windows Phone client. One of my main takeaways from that project was that it's really easy to get started with Windows Phone development, especially if you've done some Silverlight development - and I've done a good amount of Silverlight development. There are a few new concepts to wrap your mind around, but I was amazed how easy it was to get up and running.
Background Services - Start with the right mindset
You want your Windows Phone applications to do useful work in the background, but you don't want the background processes to affect the end user's phone experience. By experience, I mean:
- You don't want them to interrupt or slow down foreground tasks (especially talking on the phone) in any way, ever
- You don't want them to run down the battery
- You don't want them to run up expensive bandwidth charges or interfere with foreground network usage
- You don't want anyone to have to think about them at all
Rather than hope developers will keep the user and the above values in mind and do the right thing, Windows Phone background services provide a great framework for you to work within, and they automatically make sure you're playing within the rules. I think they've struck a great balance here, but when you're developing background services in Windows Phone, you need to keep this always in mind. Your background services are most definitely managed, and they will play within the rules. It's important to have that straight from the beginning.
Overview of services
Windows Phone background services are easier to understand when you get the idea of how they all fit together. I mentally broke them down into three generic types, with some pre-built "specialist" agents to handle common background use cases.
Note: Some of this logical grouping is stuff I came up with on my own to make sense of all of the different options. It's not officially approved by anyone but me, but it's just a mental model to help you understand what's available. Technically, if you look at the ScheduledAction class hierarchy, Resource Intensive tasks fall right next to Periodic Tasks, but for the sake of understanding what the different types of tasks and actions do, I think it's easier to ignore that for a bit.
Color coding explanation:
- Purple are custom tasks
- Green are pre-built, specialized tasks
- Blue are general types
Scheduled or Resource Intensive?
Scheduled tasks are things that don't require many resources (e.g. CPU, network) and need to run on some kind of schedule. Resource intensive tasks do require resources, so they can't dictate when they'll run.
The Windows Phone operating system keeps background tasks from abusing resources by making decisions based on how resource intensive they are. Processes that require a lot of resources can only run when conditions are met (e.g. while charging or when battery power is high), and processes that run periodically or on a schedule can't use a lot of resources.
Whatever, you say. I'll do what I want when I want because I'm a rebel. But, no, it's not that kind of setup here in background task land. For stealing tools, cooler. If your task doesn't abide by the rules, it's terminated.
Resource Intensive Tasks
Resource Intensive agents run for a relatively long period of time when the phone meets a set of requirements relating to processor activity, power source, and network connection. An example here would be performing a long-running computation, or uploading a lot of data to a service.
Since by their nature these tasks are expected to use resources, they're only allowed to run when resources are plentiful, e.g.
- External power required
- Non-cellular connection required
- Minimum battery power: 90%
- Device screen locked
- No active phone call
- Can't run longer than 10 minutes
Built-in Resource Intensive Tasks
There are two built-in resource intensive task specialists - Background Audio and Background File Transfer. Since their use-cases are more defined, some of the above requirements are relaxed.
Note: I've decided to lump background audio and background file transfer under Resource Intensive tasks because they make some logical sense there - they do suck up resources, and they aren't scheduled. That's my logical grouping, it's not anything official.
Background Audio
Background Audio lets you queue and play audio while your device isn't active or even running. I showed two examples during my talk - one which queued up a bunch of tracks which continue to play when you navigate away, and another example which plays a ticking clock in a Pomodoro application to remind you that you're still "on the clock."
The architecture of Background Audio is fascinating. It takes a little bit to figure out who's doing what, but it's really well put together. First, meet the two main players:
Audio Player Agent
It's your job to create the Audio Player Agent. You can do that by adding a new project to your solution and selecting one of the two Windows Phone Audio Agent project types (Playback or Streaming).
Of course, you can write the code by hand if you prefer, but there's little point. This project template adds the necessary references and drops a single class into your Audio Player Agent project - a class which extends the AudioPlayerAgent base class and handles two events:
OnPlayStateChanged - this is where you put the logic for when a track ends or is ready to be played
OnUserAction - this is where you respond to user input - things like play / pause / skip next / skip previous commands
The important thing to get here is that your Audio Player Agent class is completely responsive. Its only purpose is to respond to events.
Background Audio Player
There's only one Background Audio Player, and you don't own it. It belongs to the operating system, and you talk to it through its static BackgroundAudioPlayer.Instance property. Your application issues commands (requests?) to the Background Audio Player instance, which raises events in your Audio Player Agent.
So your application code talks to the Background Audio Player Instance, which talks to your Audio Player Agent - your application code never talks to the Audio Player Agent. This makes sense when you remember that your application may be tombstoned or closed immediately after talking to the Background Audio Player, which doesn't matter - the Background Audio Player calls into your agent without having to spin up your application. Here's the diagram from MSDN that shows this communication:
Note that the diagram shows the Background Audio Player is using two other operating system level components to play the music - the Zune Media Queue and the Universal Volume Control. This ends up giving a great experience to the end user with little work on your part: you queue up the audio, and the user can control the volume through a standard interface.
See the Background Audio Overview on MSDN for more information on how these parts work together.
Background Audio Tips
Here are a few good things to keep in mind when developing background audio applications.
- For development, you can use F9 (volume down), F10 (volume up), and F11 (play/pause).
- Your background audio can be either an internet URL or a file in Isolated Storage. If you have audio you need to move to Isolated Storage, see Jesse's post on how to do that.
Extra Credit: Background Audio Agent registration
Just because I'm one of those people who can't take "just because" for answer, I needed to know how the Background Audio Player knew how to find my application's Audio Player Agent. It turns out that's set up in your main application's WMAppManifest.xml (in the Properties folder). If you take a look at that, you'll see a section in there that lists tasks:
<Tasks> <DefaultTask Name="_default" NavigationPage="MainPage.xaml" /> <ExtendedTask Name="BackgroundTask"> <BackgroundServiceAgent Specifier="AudioPlayerAgent" Name="AudioPlaybackAgent1" Source="AudioPlaybackAgent1" Type="AudioPlaybackAgent1.AudioPlayer" /> </ExtendedTask> </Tasks>
Background File Transfer
The Background File Transfer service lets you register files for upload or download, and it takes it from there. It waits until appropriate conditions are met - for example, it only transfers small files over cellular networks, waiting for wi-fi or wired connection to transfer larger files - and raises events which your application can handle if it's running. It's all very fire-and-forget.
A few things to be aware of here:
- The service won't run until conditions are met. The operating system imposes some, and you can add additional restrictions. The important thing to realize here is that the conditions may never be met, so the file transfer will never happen. If you've requested a large file download which exceeds the cellular limits, but you have a user who never uses a wi-fi connection, they'll never get the file. So the responsibility is on your application to handle those exceptions.
- Mango has great support for HTTP, so you can make use of headers like If-Modified-Since and ETAGs to optimize transfers.
- You get both progress and completion events, so your UI can display status to users if they'd be interested.
- You don't need a separate agent here. You register a TransferRequest with the service and subscribe to the events, but you don't need a service because the transfer service doesn't need you to respond to anything if your application's shut down.
Custom Resource Intensive Tasks
If you need to do something resource intensive - other than audio or file transfers - you can create your own Scheduled Task Agent. You do that the same way we added the Audio Player Agent previously - there's a project type just for that. These agents just have one method: OnInvoke. That's where you do all your resource intensive what-have-you. As I mentioned earlier, there are a lot of constraints on Resource Intensive Tasks, summarized in MSDN.
My main takeaway is that you can do some neat things with these, but you need to account for two things:
- You don't know when - or if at all - your user's phone will run your task.
- If your task misbehaves, it will be terminated. Just like that.
Here's how you'd register you Resource Intensive Task:
resourceIntensiveTask = new ResourceIntensiveTask(resourceIntensiveTaskName); resourceIntensiveTask.Description = "This demonstrates a resource-intensive task."; ScheduledActionService.Add(resourceIntensiveTask);
Scheduled Tasks
As with Resource Intensive tasks, you have a few options with scheduling. If you just want to notify the user, you can take advantage of some built in notifications (alarms and reminders); if you need to do something else you can create a custom Periodic Task.
Notifications
You've got two types here: Alarms and Reminders. Both are scheduled for some time in the future with a few lines of code, and that's it as far as your app is concerned. There are a few minor differences between the two:
- Alarms will always have a title that says "Alarm" while Reminders can have a custom title
- Alarms can play a custom sound, while Reminders will always play the system reminder tone
- Tapping on an Alarm will launch the app and take you to the initial page of the application, as if it were just being launched. Reminders can include a navigation URI to a page inside your phone application, and it can contain querystring information as well.
Registering an alarm or reminder is pretty much identical, with the only difference being in supported properties. Example:
Reminder r = new Reminder("Break_Over"); r.Title = "Back To Work"; r.Content = "Break's Over!"; r.BeginTime = DateTime.Now.AddSeconds(10); r.NavigationUri = NavigationService.CurrentSource; ScheduledActionService.Add(r);
Periodic Tasks
Periodic Tasks aren't really scheduled in the way I first assumed. They can be set to run as often as 30 minutes, but that time may drift by as much as 10 minutes. They may not run every cycle, depending on how busy the operating system is. They'll be terminated if they run longer than 25 seconds, but may be terminated sooner if the operating system decides it needs to.
The point is that the Periodic Task system isn't a scheduler. It's a way to do light, low resource work in the background - small internet communications, updating a tile, etc. They're great for keeping the users phone up to date with nice to have information, but you have to assume that the task may run less often than you'd hoped, or potentially not at all.
Creating a periodic task is really easy - it's the same idea as creating a resource intensive task. You add a Scheduled Task Agent to your project, then register it with the Scheduled Action Service like this:
periodicTask = new PeriodicTask(periodicTaskName); periodicTask.Description = "This demonstrates a periodic task."; ScheduledActionService.Add(periodicTask);
A couple of tricks
Removing tasks before adding them
There are a few tricks to be aware of. First, the Periodic and Resource Intensive Tasks are registered by name - a string. So the above code I showed for both of them is overly simplistic - it will fail if you try to schedule the same task twice. The proper pattern is to find and remove the task before adding it:
periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask; if (periodicTask != null) { ScheduledActionService.Remove(periodicTaskName); } periodicTask = new PeriodicTask(periodicTaskName); periodicTask.Description = "This demonstrates a periodic task."; ScheduledActionService.Add(periodicTask);
Launch for testing
Many of these things would be tough to test in normal conditions. For example, a there's no way I want to wait around 30-40 minutes to see if my Periodic Task will run correctly. Fortunately, you can use LaunchForTest to schedule a task when you're in debug mode.
#if DEBUG_AGENT ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(60)); #endif
Toast
If you're running background processes, you may need to alert your users when things happen - completed task, failed task, significant task progress, etc. You can do that using ShellToast.
ShellToast toast = new ShellToast(); toast.Title = "Have some toast"; toast.Content = toastMessage; toast.Show();
Slides, Podcast, Code
Here are the slides for my presentation at DevConnections:
Here's the podcast with Jesse Liberty, Peter Torr, and me talking about background services and agents in Windows Phone:
Yet Another Podcast #52 - Peter Torr on Windows Phone Multitasking
Code samples from my demos are available here.
Jesse and Peter have both written about Windows Phone multitasking topics on their blogs:
- Peter Torr's Background Agents series
- Jesse Liberty's Windows Phone Multitasking series