Windows Phone 7 is not currently a
multitasking operating system, which begs the question: how does the
operating system handle application switching and state? In this
section, the Windows Phone 7 application execution model is discussed in
detail. We starts with an overview of the user experience, and then
detail what happens under the hood. Finally the section explains how to
manage application state within the available framework.
NOTE
Microsoft announced at Mobile World Conference in
February 2011 that support for multi-tasking scenarios will be added to
Windows Phone 7 later in 2011.
1. User Experience
If you have a Windows Phone 7 device, you are familiar with the navigation model. You know that the Start and the Back
hardware button play an important role in the navigation model. The
navigation model is similar to web browser navigation, which suggests
that the phone maintains a back-stack of pages previously
visited. Given that third-party applications do not multitask with other
third-party applications in the current release, this brings up an
intriguing question: what happens when you navigate back to a previous
third-party application?
You probably noticed in previous examples that if you
dynamically loaded data either via code or a remote service call, if
you hit the Start button and then the Back button, the Resuming...
screen is displayed and the application UI is rendered, but is missing
the dynamic data. This happens because, while the operating system keeps
a record in the back-stack of which apps have run previously, it does
not maintain application state. Applications have a responsibility with
the operating system to restore the application to its previous state.
2. Event Lifecycle
If you are a Windows Forms or WPF developer, you are familiar with these four events related to application life cycle:
Launching: Application initially loads
Closing: Application is excited by the user
Activated: Application comes to the foreground
Deactivated: Application loses focus
On Windows Phone 7, these events retain their general meaning, but with a twist. Launching and Closing work as expected. The Launching event fires upon initially launching the application. Closing fires upon exit.
The Deactivated event fires when the
application loses focus when a hardware button is tapped, a phone call
comes in, and so on. But the twist is that instead of continuing to run,
the application is shut down, and an entry in the back-stack is made.
If the user navigates back to the application via the placeholder in the back-stack, the Activated event fires and the application resumes. Even though the application is no longer running when the Deactivated event fires, the Closing event does not fire. Same when the application launches upon resume in the Activated event; the Launching event does not fire.
For this section, the code for calling REST+JSON services in the CallingRemoteServices
project is copied into this project to give us some interesting data to
work with. We add additional code to log what is happening via the
Visual Studio Output window using Debug.WriteLine, so as to not interfere with basic application functionality, but to let us know what is happening.
NOTE
Configure both the WcfRemoteServicesSimpleRestJSON and AppExecutionModel projects as startup projects for the solution to ensure the service is running.
The application lifecycle events are not attached to the individual pages, like MainPage.xaml. Instead, the events are attached to the Application object directly via the Microsoft.Phone.Shell.PhoneApplicationService class as part of the Application.ApplicationLifetimObjects
collection. The lifecycle events are already created for you when you
start a new project. In this project a few more events are added to the
code:
Application_Startup
Application_Exit
MainPage - PhoneApplicationPage_Loaded
MainPage - PhoneApplicationPage_Unloaded
MainPage - PhoneApplicationPage_BackKeyPress
The application uses Debug.WriteLine("") to
log when in an event handler. Let's now run the application, close the
application, and then review the logged events in the Output Window.
In App constructor
In Application_Startup
In Application_Launching
In MainPage Constructor
In PhoneApplicationPage_Loaded
In PhoneApplicationPage_BackKeyPress
In Application_Closing
In Application_Exit
Let's next load the application, click the Start
button, navigate back, and then hit the back button again to exit the
application.
In App constructor
In Application_Startup
In Application_Launching
In MainPage Constructor
In PhoneApplicationPage_Loaded
In Application_Deactivated
'taskhost.exe' (Managed): Loaded 'System.Runtime.Serialization.dll'
In Application_Exit
...navigate to Start screen,application tombstones, and then navigate back to application
In App constructor
In Application_Startup
In Application_Activated
In MainPage Constructor
In PhoneApplicationPage_Loaded
In PhoneApplicationPage_BackKeyPress
In Application_Closing
In Application_Exit
You can see that the Page and Application constructors and Page_Loaded fire every time, whether as part of initial launch or when resuming from tombstoning. Same for Application_Exit.
It fires whether tombstoning or actually exiting the application. You
will really want to understand this event cycle to ensure that you place
code in the appropriate event handler, depending on what's desired. The
next section discusses how to maintain state.
3. Managing State
Let's now add state management to the code copied from the AdventureWorksRestJSONPage page in the CallingRemoteServices project. First, let's generate random state in the App() constructor and store in a variable named randomState as a string. The state will be the current DateTime.Now value when the application is launched. It will be saved in Application_Deactivated, and restored in Application_Activated. The application will use Debug.WriteLine to log the state in App.xaml.cs. Here is the code to initialize state:
if (PhoneApplicationService.Current.StartupMode ==
StartupMode.Launch)
{
Debug.WriteLine("In App constructor");
randomState = DateTime.Now.ToString();
Debug.WriteLine("Random State: = " + randomState);
}
Notice that the code in App() to initialize state
checks to ensure the application is Launching and not Activating. This
is because the constructor is called in both cases as shown in the
previous section.
The recommended location to store application state is in PhoneApplicationService.Current.State, which is of type IDictionary<string, object>. The "key" is a string, and the state is of type object, which means it can store any serializable object, including complex objects that contain objects. Here is the code to save in Application_Deactivated and restore in Application_Activated:
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
Debug.WriteLine("Activating - load state");
IDictionary<string, object> state =
PhoneApplicationService.Current.State;
if (state.ContainsKey("Random State"))
{
randomState = state["Random State"] as String;
}
Debug.WriteLine("Random State Restore - " + randomState);
Debug.WriteLine("In Application_Activated");
}
// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
Debug.WriteLine("Deactivating - save state");
IDictionary<string, object> state =
PhoneApplicationService.Current.State;
state["Random State"] = randomState;
Debug.WriteLine("In Application_Deactivated");
}
The Application_Deactivated event adds the data to the State object. The Application_Activated event restores state, but first uses State.ContainsKey to make sure the state information is present before trying to retrieve the state using the key value.
Applications have 10 seconds in both
Application_Deactivated and Application_Activated to complete work.If
saving or loading data goes beyond 10 seconds, the operating system will
end the event handler.
|
|
The following is the data from the Output window after launching the app, clicking the Start button, and then the Back button:
In App constructor
Random State: = 1/24/2011 11:59:26 PM
In Application_Startup
In Application_Launching
In MainPage Constructor
In PhoneApplicationPage_Loaded
Deactivating - save state
In Application_Deactivated
In Application_Exit
In Application_Startup
Activating - load state
Random State Restore - 1/24/2011 11:59:26 PM
In Application_Activated
In MainPage Constructor
In PhoneApplicationPage_Loaded
In PhoneApplicationPage_BackKeyPress
In Application_Closing
In Application_Exit
As an alternative, you can handle state management at
the page level. In many ways, this method makes more sense, because it
is easier to restore the data as close to where it is needed, as opposed
to restoring in App.xaml.cs and then passing it along. The code in MainPage.xaml.cs overrides the OnNavigatedFrom and OnNavigatedTo methods as shown here:
protected override void OnNavigatedFrom(
System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
IDictionary<string, object> state = PhoneApplicationService.Current.State;
state["Selected Item"] = VendorsListBox.SelectedIndex;
DataStore.Instance.SaveCollection<ObservableCollection<Vendor>>(
DataStore.Instance.Vendors, "Vendors");
}
protected override void OnNavigatedTo(
System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (PhoneApplicationService.Current.StartupMode ==
StartupMode.Activate)
{
//Load data from isolated storage
DataStore.Instance.Vendors =
DataStore.Instance.LoadCollection<ObservableCollection<Vendor>>(
DataStore.Instance.Vendors, "Vendors");
VendorsListBox.ItemsSource = DataStore.Instance.Vendors;
// The state bag for temporary state
IDictionary<string, object> state =
PhoneApplicationService.Current.State;
// See if the bag contains the selected item
if (state.ContainsKey("Selected Item"))
{
//Set selected item on page
VendorsListBox.SelectedIndex = (int)state["Selected Item"];
//Scroll to selected item
VendorsListBox.ScrollIntoView(
VendorsListBox.Items[VendorsListBox.SelectedIndex]);
}
}
}
Notice that the code does not save the Vendors collection to the PhoneApplicationService.Current.State
object. The application could do that, but it seems to make sense to
persist the major data collections where they would normally be saved,
as opposed to being temporary state. On the other hand, saving the
selected item makes sense, because when the user returns to the
application after resume, the user will expect to see the application
state just as they left it.
When you run the application, retrieve it from the service and then tombstone it by clicking a hardware button; then click the Back button to resume. Notice that the data is restored, and the current item is selected in the VendorsListBox control. To the user, it is as if the application continued to run when it really hadn't.
Understanding and supporting good life-cycle
management is critical to providing a robust user interface. With good
state management, a user will not even notice that the application
stopped running.
4. Running Under Lock Screen
You can configure an application to run under the
lock screen. This means that when the phone lock careen activates, the
application continues to run in the foreground and is not tombstoned.
This also means that the application continues to consume CPU and more
importantly battery when running under the lock screen. Here is a link
to the documentation:
http://msdn.microsoft.com/en-us/library/ff941090%28v=VS.92%29.aspx
There are some application scenarios in which running
under the lock screen greatly enhances the user experience. An
application that records GPS values while running under lock for a run
tracker program is an excellent example.
It is pretty straightforward to implement running
under the lock screen. Essentially configure
ApplicationIdleDetectionMode to disabled in App.xaml.cs:
PhoneApplicationService.Current.ApplicationIdleDetectionMode = IdleDetectionMode.Disabled;
Handle theApplication's RootFrameObscured
and UnObscured events. When the application is Obscured, execute code
to reduce CPU cycles as much as possible to conserve battery. In the
UnObscured event, restart application functionality to restore the
application or game.