Mobile Matters - Windows Phone 7 Tombstoning
By Jaime Rodriguez | May 2011
A good mobile platform should acknowledge the hardware constraints that mobility imposes on the device. Compared to desktops, mobile devices have less memory, less processing power, limited screen real-estate and limited battery life. Add up these constraints and you should conclude that, on a non-dedicated device where many applications will run, applications will eventually be closed or shut down to make resources available to other applications.
Windows Phone deals with this constraint through a feature called tombstoning. While it may seem like tombstoning would be a straightforward proposition, it’s actually a point of contention among developers. Some argue it shouldn’t be needed. Others argue that it’s too difficult. The rest of us simply hate the name of the feature. Still, mobile device constraints make it a necessary evil, so great mobile applications must be able to handle tombstoning.
Windows Phone Application Lifecycle
Most new Windows Phone developers come to the platform with the expectation that an application’s lifecycle looks something like:
- Go back to 1 and start again
Windows Phone 7 challenges this expectation by exposing a different lifecycle—one that’s less process-oriented and more session-oriented.
In Windows Phone 7 you should think of the lifecycle as:
- Interrupted execution or exit
- If interrupted, come back—or, even if interrupted, start anew
- If exited, start anew
The benefit of this new session-oriented model is that users can navigate across apps without having to think about how the OS is managing its resources. A user doesn’t care if interrupting their game to reply to an incoming SMS message will kill the process for the game. The user should expect that he can get back to the game when finished with the message. If that works well enough, the underlying details are irrelevant.
The downside for developers is that there’s a bit more to handle to truly provide the perception of session continuity because your sessions are still running on a traditional, process-centric OS. To accommodate sessions in a process-centric world, you create logical states for the session: Launched, Activated, Running, Deactivated, Tombstoned and Closed (or Ended).
Figure 1 shows the practical lifecycle for a Windows Phone 7 app. The application lifecycle events, described in Figure 2, are exposed by the Microsoft.Phone.Shell.PhoneApplicationService class.
Figure 1 Windows Phone 7 Application Lifecycle
Figure 2 Application Lifecycle Events
|Logical State||PhoneApplicationService Event||Description|
|Launched||Launching||Application is launched when the user presses the start tile or the application list icon, or the user clicks on a toast notification. Launched is a fresh start for the session.|
|Activated||Activated||Application is activated when the user presses the Back button and brings the application that had previously been deactivated back to the foreground. In this case, the user expects to be coming back to an ongoing session.|
|Running||Running||After being either launched or activated, the application is running.|
|Deactivated||Deactivated||An application that’s running is deactivated when foreground processing is transferred from this application to another application or to an OS component (such as a Chooser or a Launcher or the lock screen). The session is interrupted, but is expected to be resumed later.|
|Ended (or Exited)||Closing||
User is exiting the application by pressing the back key on the main page.
When exiting, the user expects to come back to a new, fresh application.
The tombstoned state is a bit more complicated and not directly related to a PhoneApplicationService event. When an application is deactivated, the OS does not immediately kill the process for that application. In theory, the OS kills the application when it needs resources and when it happens. The application doesn’t get a notification at all, and it’s simply killed.
In practice, Windows Phone 7 kills the process quickly after control is transferred to another foreground application, but this is not a detail you should count on. Microsoft already announced at Mobile World Congress last February that improvements such as Fast App Switching are coming, so don’t rely on an implementation detail to determine when tombstoning happens. Instead, prepare for it by doing the right work at deactivation.
Deactivated vs. Tombstoned
A phone has many processes running all the time (shell, phone and so on), but it has, at most, one application running in the foreground. (It can have zero when nothing is running in the foreground.)
When a foreground application transfers control to another application or to an OS component, it gets deactivated. After a process is deactivated, the OS might kill the process to release the resources. This is called tombstoning.
As you can see, tombstoning does not happen every time the app is deactivated, but tombstoning always comes after a deactivation. In fact, Deactivated is the last event PhoneApplicationService fires before tombstoning, so this is where you must do your work to activate the app again later.
Figure 3 shows all the different tasks that lead to deactivation, and guesses the likelihood of tombstoning occuring.
Figure 3 Deactivation Tasks
|User presses the Back button on the first page of the application||No; this closes the app||No. Deactivation never happened.|
|User presses the Start button||Yes||Very likely, but not guaranteed. A few seconds after the app is deactivated, it’s tombstoned. If the user comes back to the app quickly after deactivation, it might not tombstone. This could be considered an indeterminate timeout.|
|User invokes a chooser or a launcher that tombstones||Yes||Very likely, but timeout applies.|
|User invokes a chooser or a launcher that doesn’t tombstone||Yes||Less likely, but it can still happen. If the user presses the Start button in the middle of the task, then sees the “User presses the Start button” rule. A new Deactivated event is not fired because the app was already deactivated.|
|Lock screen comes up and app is not configured to run under lock||Yes||Very likely, but timeout applies.|
|A toast notification comes in and user taps on it, transferring to another foreground application||Yes||Very likely, but timeout applies.|
There’s a subset of choosers that don’t tombstone immediately, but can still be tombstoned if the user takes an action that tombstones the process. These include PhotoChooserTask (unless the user specifies crop), CameraCaptureTask, MediaPlayerLauncher, EmailAddressChooserTask and PhoneNumberChooserTask.
All other choosers and launchers tombstone right after the Show method is called.
To see the Windows Phone 7 application lifecycle in action, launch the LWP.TombStoning sample in the code download.
Saving and Restoring State
Because the goal for session-based navigation is to make it easy for the user to jump across foreground applications seamlessly, you must save all relevant state in the Deactivated event and restore state in the Activated event. Most applications have three types of state to manage:
- Persistent application state must always be persisted. This includes application settings, user data and so on.
- Session-specific application state includes temporary state such as caches and ViewModels that need to be restored in activation, but are started anew in a fresh launch of the app.
- UI- or page-specific stateis needed to restore a PhoneApplicationPage when an app is activated. Windows Phone 7 saves the back stack for an application when tombstoning. Upon activation it restores only the last active page before the application was tombstoned. If the user presses the Back button, the previous page gets instantiated.
Persistent application state should be saved in IsolatedStorage or via the ApplicationSettings class. Application state should be saved as early as possible, in case the battery runs out, for example.
If it’s user data (such as a session cache) and you don’t want to serialize too often, then it should be saved both on the Deactivated and Closing events, and (symmetrically) it should be restored in the Activated or Launching events.
Session-specific state can be saved in isolated storage if you want control over serialization formats or you have too much data, or it can be saved in the PhoneApplicationService.State dictionary. You should save it only in the Deactivated event and restore it in the Activated event.
Page-specific state should be saved in the PhoneApplicationPage.State dictionary. The key to saving page state is to remember that the application has a back stack of pages that will be serialized automatically when PhoneApplicationService.Deactivated occurs. To keep your pages ready for tombstoning, you must listen for the PhoneApplicationPage.OnNavigatedFrom override in your page and save any view state that’s not yet committed to your Model (or ViewModel) in the page’s dictionary. Don’t wait until the Deactivated event, because by then you can’t get to the pages in the back stack.
Of course, if you save page state when you recieve OnNavigatedFrom, you should restore it in the OnNavigatedTo override for the page.
You could also save page-specific state in ViewModels, and then serialize your ViewModels as session state, but that would require saving uncommitted state on a ViewModel, so I don’t recommend it. Leverage the infrastructure in place to stay future-proof for later optimizations in the platform.
Tombstoning is not difficult. It’s just a bit of tedious work and it demands consistency and planning. If your app is deactivated but not tombstoned, your state will remain in memory and will not be reconstructed.
Avoid relying on class constructors that create state that your application needs, but that might be released during deactivation. Symmetry is preferred. Use the PhoneApplicationService Deactivated and Activated events for app-level state, and OnNavigatedFrom or OnNavigatedTo for page state.
If you have objects (singletons) in your app that are instantiated outside of activation calls (maybe due to delay instantiation), always check whether they’re properly constructed and initialized before trying to use them. A common mistake that I’ve encountered is reading data in the PhoneApplicationService.Activated event or PhoneApplicationPage.OnNavigatedTo and not resetting it. Pages can be NavigatedTo multiple times (regardless of tombstoning) and even a session can be tombstoned multiple times during a session.
After you’ve restored state, clear it. You can set it later in theNavigatedFrom override for your page or the Deactivated event for the app.
Be smart about what you save and when you restore it. Part of making it seamless for the user to come back to the app is restoring your application promptly. If you save too much of either page or application state, it will slow down activation. Leverage isolated storage if needed to background load state that might not be required immediately when your application is activated. Both activation and deactivation should happen in less than 10 seconds. Be aware of this or the OS may kill your process before it finishes deactivating or as it’s reactivated. You should, however, aim for much less than 10 seconds.
Understand the constraints of the serialization framework. You can, at most, store around 2MB across all of your page and application state. If your total adds up to more, you’ll start seeing exceptions when you navigate and when you deactivate. You shouldn’t be serializing this much data in page state. If you need to cache large data sets, keep them in isolated storage.
Use query strings for page navigation. If you must pass context into a new page, use the query string passed to the page to either pass all the data or pass a unique identifier (a token) to a service locator that can fetch the data based on that token. Don’t assume ViewModels or page state are available when your pages are activated after tombstoning.
Understand your choosers and launchers. Not all of them tombstone, and there are specific rules on how to wire up event listeners for choosers. For these rules, please read “How to: Use Choosers for Windows Phone” at msdn.microsoft.com/library/windows/apps/ff769543(v=vs.105).aspx.
Be mindful of the relationship between OnNavigatedTo and PhoneApplicationPage.Loaded. Within OnNavigatedTo, the visual tree for the page is not fully built yet. Often you’ll have to extract restored state, but wait until the page’s Loaded event to restore the UI state. Examples of actions that must be delayed include setting Focus, setting SelectedIndex in a pivot and scrolling.
If you’re doing a lot to save and restore data—and you shouldn’t be—consider some advanced optimizations. Note that you should do this only if necessary and make sure to test thoroughly.
To detect tombstones, set a flag on your app class in Deactivated. If the flag isn’t reset on Activated, it means you weren’t tombstoned and all your pages should still be in memory and require no restoration. To combine it with detection for tombstoning in pages, you can use a token for each activation within a session.
Another optimization is to listen for the page’s OnNavigatingFrom override and detect direction. If NavigationMode is going back, the page will be destroyed and there’s no reason to save state for it.
Again, planning is the key to proper tombstoning. Don’t leave tombstoning for the end of the app development cycle and try to retrofit it. Plan it early, implement it as you go and test thoroughly.
You Can Make It Great
One last tip for developers is to think hard about making the experience seamless. Windows Phone 7 controls allow you to get around a page easily. To make it truly seamless for the user—to feel like he never left a page or app—you should consider restoring the following:
- SelectedIndex for the pivot (if the page contains a pivot)
- Scrolling position in a ListBox or any other ScrollViewer in the page
- Zoom levels and other transforms in a map control, picture viewer or any other control that supports manipulation
- Uncommitted text in a TextBox; if the TextBox is critical to the app (the tweet text in a Twitter app, for example) consider restoring text selection, caret position and focus
- Element that had focus in a page (especially if it’s a TextBox, which needs to show SIP)
The one you shouldn’t restore is SelectedItem in a panorama. The panorama doesn’t support this, and setting the DefaultItem in a panorama is not the same as panning to the correct page. I recommend that you avoid using DefaultItem as a means to get back to the panorama item selected before tombstoning.
To see these tips in action, launch the LPW.TombstoningWithState sample in the code download. The readme.txt file has pointers and scripts for each scenario.
A Few Last Remarks
This article is by no means a comprehensive reference to tombstoning. But now that you know what to look for, start introducing some tombstoning patterns into your own Windows Phone 7 apps. I think you’ll see right away how much it improves the user experience.
Jaime Rodriguez is a principal evangelist at Microsoft driving adoption of emerging client technologies such as Silverlight and Windows Phone 7. You can reach him on Twitter at twitter.com/jaimerodriguez or read his blog at blogs.msdn.com/jaimer.
Thanks to the following technical expert for reviewing this article: Peter Torr