Restoring Your Application After Deactivation
October 21, 2011
When the user navigates away from your application, it goes dormant. If there is not enough memory available, your application may be tombstoned. If the user returns to your application, you will need to restore your application from the appropriate state.
You Will Learn
- What the dormant state is.
- What tombstoning is.
- How to restore your application after it has been put in a dormant state or been tombstoned.
- When to perform other actions in your application.
Applies To
When to Perform Actions in Your Application
A Windows Phone application launches when the user taps its application icon or tile and closes when the user presses the Back button from the application's start page. External events (such as pressing the Start button) can deactivate the application at any time, potentially tombstoning it and causing it to terminate. Reactivation reloads the application and enables it to restore its previous state. These startup and shutdown stages are represented by the Launching, Activated, Closing, and Deactivated events of the PhoneApplicationService class.
While an application is launched, it navigates to the start page automatically, after which the user can navigate to additional pages. If the application is deactivated and reactivated, it navigates to the previously active page.
When an application first navigates to a page, or navigates back to a page after deactivation and reactivation, it instantiates the page, calls its constructor, and then calls its OnNavigatedTo method override, if present. Just before it navigates to a different page, exits, or deactivates, it calls the OnNavigatedFrom method. Page instances are reused only when the user navigates back to a previously visited page and the application has not been deactivated since that visit. In this case, the application calls the OnNavigatedTo and OnNavigatedFrom methods as before but without calling the constructor first.
For more information about the application lifecycle including the sequence of lifecyle events, see Execution Model Overview for Windows Phone.
Tombstoning Overview
Your application can be interrupted at any time, typically when a user navigates to another application using the Start button. When an application is deactivated, it will first go into the dormant state. In this state, the operating system will preserve your application threads and save all your state in memory. However, if there is not enough memory for the other applications running, your application may be terminated using a procedure called tombstoning, which stores a record of the application in memory and associates it with your cached state data. The data that is saved includes the currently displayed application page and the back stack of pages that had previously been visited in the application.
Certification Requirement: |
---|
Your application must be responsive on starting and when responding to user input. To help meet the first requirement, you can display a splash screen. To help meet the second requirement, you can load data only when it is needed, and start time-consuming operations asynchronously, perhaps using a BackgroundWorker. Even if you use a background thread, however, you should launch it in an OnNavigatedTo method override and not a Launching or Activated event handler to avoid contributing to startup delays. For details, see section 5.2 in Technical Certification Requirements. |
When tombstoning occurs, if the user navigates back to your application, the operating system restarts your application process and passes the state data back to it. You should save the application state so that it can be restored in case of reactivation. Isolated storage is too slow for transient state data. Instead, you will cache the data using in-memory state dictionaries in order to save and load the data rapidly.
Tip
When an application is interrupted and then resumed, users expect it to continue where it left off. For this reason, you should always implement tombstoning support for any page that can change its state. This applies to state changes as complex as user data entry updates or as simple as the user changing the current selection in a list control. Failure to support tombstoning may result in poor usability at best and completely unexpected behavior at worst.
Certification Requirement: |
---|
Your application must be able to perform correctly when the user is receiving a phone call, an SMS message, or an MMS message. For details, see section 5.3 in Technical Certification Requirements. |
Implementing Dormant State and Tombstoning Support
When your application is deactivated, it first goes into the dormant state. In this case, all your state data is handled by the operating system. It is important to understand that your application may be tombstoned at any time after deactivation. Therefore, you should use the temporary cache to store state, by doing the following:
- In the Deactivated event handler, store application-level state data to the PhoneApplicationService.State property in a Deactivated event.
- In the OnNavigatedFrom method override, store page-level state data to the PhoneApplicationPage.State property of each page.
Whenever your application is reactivated, you always want to first check if it has come out of the dormant state. This can significantly improve the start-up performance since all your state is still in memory and you do not need to load it. If you have been tombstoned, you will need to retrieve your state data from the state dictionary.
- In the Activated event handler, first check the IsApplicationInstancePreserved flag and if false, retrieve application-level state data.
- In the OnNavigatedTo method override, check to see whether the page is a new instance and if so, retrieve page-level state data.
These two State properties represent dictionaries in which you can store key/value pairs where the values are primitive value types or serializable types. Any data that must be retained should be stored in these dictionaries so that when the application is reactivated it features a reduced load time. In addition, during the Deactivated event you should also store to isolated storage any state that should persist across application instances.
Performance Tip: |
---|
Use state dictionaries to restore state. Avoid restoring state from isolated storage or the web. |
The Fuel Tracker application saves temporary page-level state during tombstoning. Each page stores state data to its PhoneApplicationPage.State property. For example, the FillupPage class contains a Fillup object bound to some text boxes on the user interface. This object is saved to long-term storage only when the user taps the Save button. During tombstoning, however, the page stores the object in the page State dictionary along with a value that indicates whether the user has changed it, as shown in the following code. (The Save button implementation is described in Validating Data Entry Input.)
private const string CURRENT_FILLUP_KEY = "CurrentFillup";
private const string HAS_UNSAVED_CHANGES_KEY = "HasUnsavedChanges";
private Fillup _currentFillup;
private bool _hasUnsavedChanges;
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Do not cache the page state when navigating backward
// or when there are no unsaved changes.
if (e.Uri.OriginalString.Equals("//Views/SummaryPage.xaml") ||
!_hasUnsavedChanges) return;
CommitTextBoxWithFocus();
State[CURRENT_FILLUP_KEY] = _currentFillup;
State[HAS_UNSAVED_CHANGES_KEY] = _hasUnsavedChanges;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Initialize the page state only if it is not already initialized,
// and not when the application was deactivated but not tombstoned
// (returning from being dormant).
if (DataContext == null)
{
InitializePageState();
}
// Delete temporary storage to avoid unnecessary storage costs.
State.Clear();
}
private void InitializePageState()
{
CarHeader.DataContext = CarDataStore.Car;
DataContext = _currentFillup =
State.ContainsKey(CURRENT_FILLUP_KEY) ?
(Fillup)State[CURRENT_FILLUP_KEY] :
new Fillup { Date = DateTime.Now };
_hasUnsavedChanges = State.ContainsKey(HAS_UNSAVED_CHANGES_KEY) &&
(bool)State[HAS_UNSAVED_CHANGES_KEY];
}
Private Const CURRENT_FILLUP_KEY As String = "CurrentFillup"
Private Const HAS_UNSAVED_CHANGES_KEY As String = "HasUnsavedChanges"
Private _currentFillup As Fillup
Private _hasUnsavedChanges As Boolean
Protected Overrides Sub OnNavigatedFrom(ByVal e As NavigationEventArgs)
MyBase.OnNavigatedFrom(e)
' Do not cache the page state when navigating backward
' or when there are no unsaved changes.
If e.Uri.OriginalString.Equals("//Views/SummaryPage.xaml") AndAlso
Not _hasUnsavedChanges Then Return
CommitTextBoxWithFocus()
State(CURRENT_FILLUP_KEY) = _currentFillup
State(HAS_UNSAVED_CHANGES_KEY) = _hasUnsavedChanges
End Sub
Protected Overrides Sub OnNavigatedTo(ByVal e As NavigationEventArgs)
MyBase.OnNavigatedTo(e)
' Initialize the page state only if it is not already initialized,
' and not when the application was deactivated but not tombstoned
' (returning from being dormant).
If DataContext Is Nothing Then
InitializePageState()
End If
' Delete temporary storage to avoid unnecessary storage costs.
State.Clear()
End Sub
Private Sub InitializePageState()
CarHeader.DataContext = CarDataStore.Car
_currentFillup =
If(State.ContainsKey(CURRENT_FILLUP_KEY),
State(CURRENT_FILLUP_KEY),
New Fillup() With {.Date = DateTime.Now})
DataContext = _currentFillup
_hasUnsavedChanges =
If(State.ContainsKey(HAS_UNSAVED_CHANGES_KEY),
State(HAS_UNSAVED_CHANGES_KEY), False)
End Sub
In this code, the OnNavigatedFrom method override first checks whether it should cache the state data. The page caches the data only when forward navigation occurs and only when there are unsaved changes.
If the changes are being cached, the OnNavigatedFrom method commits the text box values to the bound object (stored in the _currentFillup field), and then saves the _currentFillup and _hasUnsavedChanges values to the State dictionary. Committing the text box with focus value is necessary because the data binding engine normally commits text-box values only when the control loses focus, which does not occur automatically when an application is deactivated.
Tip
When an application is unloading, it must also complete all operations within 10 seconds of the request to close or deactivate, or it will be immediately terminated. To help meet this requirement, you should consider saving your data as soon as it is available or incrementally if there are large amounts of data.
The OnNavigatedTo method calls the InitializePageState method if a page is a new instance. The InitializePageState method sets the CarHeader element's DataContext property and then attempts to retrieve the stored values from the State dictionary. If the values are present in the dictionary, they are used to set the page's DataContext property in addition to the _currentFillup and _hasUnsavedChanges fields. Otherwise, default values are used. Finally, the dictionary is cleared to avoid consuming space unnecessarily.
This implementation provides basic tombstoning support, but does not store every aspect of the page state. Specifically, this implementation does not store a value that indicates which text box has focus, nor does it store the cursor position and selection state of the focused text box. The importance of saving this state information depends on the application, but for implementation suggestions, see How to: Preserve and Restore Page State for Windows Phone. For more information about tombstoning, see the Tombstoning QuickStart.