Fast App Switching (FAS) is a
feature that places an app in a suspended state when the user navigates
away from it. The app’s state is preserved in memory. If the user
returns to the app by tapping the hardware Back button or by using the
task switcher, the app instance resumes. Because the app is preserved
in memory, the app is returned to the same state it was when the user
navigated away.
By default, if the app is suspended and the
user relaunches the app by tapping on the app name in the app list or
tapping the app’s primary Tile, the suspended instance of the app is
terminated and a new instance of the app is created. This process is
slower than resuming a suspended app.
Windows Phone 8 introduces a new
feature called Fast App Resume that gives apps the capability to
request that user actions that would typically relaunch the app, such
as tapping the app’s primary tile, instead resume the suspended app
instance, if one exists.
Enabling Fast App Resume
Enabling Fast App Resume requires a
modification to your app’s WMAppManifest.xml file. Add the
ActivationPolicy attribute to the DefaultTask element and set the value
to Resume, as demonstrated in the following example:
<DefaultTask Name="_default" NavigationPage="MainPage.xaml"
ActivationPolicy="Resume" />
If ActivationPolicy
is not specified, the default value of Replace is used.
Optimizing the Resume Experience
Fast App Resume in your app should be
implemented in a manner that ensures a predictable experience for the
user. The behavior of your app when it is resumed should depend on the
entry point of your app when it was initially launched from a cold
start and the entry point when it was resumed.
There are two kinds of entry points. Primary
entry points occur via a primary tile on the Start Experience or an app
link in the App List or Games hub. Secondary entry points occur via a
deep link from a secondary tile, a toast notification, or from an
extensibility point such as a protocol handler.
The Windows Phone OS maintains a navigation
history for your app. When the user taps the hardware Back button, the
OS navigates back through the stack of previously visited pages. With
Fast App Resume the OS attempts to navigate to a URI within your app.
If the URI is different from the current page of the app, a new page
instance is created and placed on the navigation back stack. If the URI
is identical to the app’s current location, no new page is created and
the app’s current page is displayed.
If the entry point is your app’s primary
tile, for example, the URI is your app’s main landing page. When an app
is resumed via a primary entry point, it can respond in one of the
following two ways:
- It can start fresh by clearing the back stack and navigating to the
app’s main landing page. If the user navigates backward from the
resumed app, because there are no pages in the history the app will
exit, returning the user to the Start Experience or the previous app.
- It can resume as if nothing happened by canceling navigation to the
requested URI. In this case, the user arrives on the page that he last
viewed and the back stack from the previous session remains intact.
Although it is possible to resume as if
nothing happened whenever the user launches your app from a primary
entry point, after a few minutes of inactivity it is better to start
fresh. The user may no longer be mindful of the app’s presence in the
background and will expect that relaunching the app starts a new
instance. In the sample for this section, you see how to detect and
expire a Fast App Resume in this scenario.
Implementing Fast App Resume gets more
complicated when the user launches or resumes an app from a secondary
entry point. For example, if the user launches the app via its primary
tile and then taps the hardware Start button, the app is deactivated
and waits to be resumed. If the user then navigates to the Photo hub
and relaunches the app from the picture viewer, the app should resume
by clearing the back stack and navigating to the requested deep link
URI.
Table 1
shows the recommended behavior for resuming via a primary or secondary
launch point after being launched from either a primary or secondary
entry point.
TABLE 1. Fast App Resume Recommended Resume Behavior
An appropriate place for detecting and coordinating Fast App Resume is in your project’s App
class. The App
class in the WPUnleashed.Examples project, in the downloadable sample code, responds to the various Fast App Resume scenarios.
When the sample app is deactivated, the App
class records the time of deactivation in the app’s isolated storage
settings. When the app is activated, a field is set to the result of
the HasFastResumeExpired
method, which is shown in Listing 1.
Although it is not absolutely necessary for
the purposes of Fast App Resume to store the deactivation time in
isolated storage, it does allow the value to be used for other purposes
in which tombstoning may occur.
LISTING 1. App.HasFastResumeExpired
Method
bool HasFastResumeExpired()
{
DateTimeOffset lastDeactivated;
if (settings.Contains(DeactivationTimeKey))
{
lastDeactivated = (DateTimeOffset)settings[DeactivationTimeKey];
}
TimeSpan duration = DateTimeOffset.Now.Subtract(lastDeactivated);
return TimeSpan.FromSeconds(duration.TotalSeconds)
> fastResumeExpiryTime;
}
You saw earlier that an app can be launched or resumed via a primary or secondary entry point. The App
class uses a custom enum named SessionType
to record if the app is navigating to the app’s home page via a primary
entry point or to a deep link URI via a secondary entry point. SessionType
can be either None, Home, or DeepLink.
Each time a navigation event is raised, the App
class’s RootFrame
_Navigating
handler determines the SessionType
using the NavigatingCancelEventArgs
object’s Uri
property (see Listing 2).
If the session type has not been recorded and the navigation mode is
set to New, it indicates that the app is launching. When the navigation
mode is equal to Reset, a Fast App Resume is underway.
During a Fast App Resume, if the current page URI is not the same as the requested URI, the root frame’s Navigating
event is raised twice; once for the page that was displayed before
deactivation and once for the requested URI. If the current page URI is
the same as the requested URI, the Navigating
event is raised only once for the page that was displayed before deactivation.
LISTING 2. App.RootFrame_Navigating
Method
void RootFrame_Navigating(object sender, NavigatingCancelEventArgs e)
{
string url = e.Uri.ToString();
if (sessionType == SessionType.None && e.NavigationMode == NavigationMode.New)
{
/* App is launching. */
if (url.Contains("?"))
{
sessionType = SessionType.DeepLink;
}
else if (url.Contains("/MainPage.xaml"))
{
sessionType = SessionType.Home;
}
}
if (e.NavigationMode == NavigationMode.Reset)
{
/* The current navigation is the result of a Fast App Resume.
* If so the URI is different from the current page URI
* then another navigation will follow. */
lastNavigationWasReset = true;
}
else if (e.NavigationMode == NavigationMode.New && lastNavigationWasReset)
{
/* This block will run once if the previous navigation
* was the result of a Fast App Resume. */
lastNavigationWasReset = false;
if (url.Contains("?"))
{
/* We reach this point if a secondary entry point was used,
* such as via the secondary tile
* created in FastAppResumeViewModel.cs */
sessionType = SessionType.DeepLink;
/* The page navigation stack will be cleared. */
}
else if (url.Contains("/MainPage.xaml"))
{
if (sessionType == SessionType.DeepLink)
{
/* When the app was previously launched via a deep link URI
* and relaunched via its primary tile,
* the back stack needs to be cleared. */
sessionType = SessionType.Home;
}
else
{
if (!fastResumeExpired)
{
/* The app was previously launched via its primary tile
* and relaunched via its primary tile.
* Cancel the navigation to resume. */
e.Cancel = true;
shouldClearJournal = false;
}
}
}
fastResumeExpired = false;
}
}
The root frame’s Navigated
event is raised after its Navigating
event, unless the Cancel
property of the NavigatingCancelEventArgs
is set to true.
The RootFrame_Navigated
handler, shown in Listing 3, keeps track of whether the previous navigation was a result of a Fast App Resume, which is indicated when the NavigationMode
property of the NavigationEventArgs
is equal to Reset. If so, and the timeout period has not expired, the page navigation stack is cleared using the RemoveBackEntry
method of the root frame.
LISTING 3. App.RootFrame_Navigated
Method
void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
NavigationMode mode = e.NavigationMode;
if (previousNavigationWasReset && mode == NavigationMode.New)
{
if (shouldClearJournal)
{
Debug.WriteLine("Clearing history.");
/* Clear the entire page stack. */
var rootFrame = (PhoneApplicationFrame)RootVisual;
while (rootFrame.RemoveBackEntry() != null)
{
/* Nothing to do. */
}
}
else
{
shouldClearJournal = true;
}
}
else
{
previousNavigationWasReset = mode == NavigationMode.Reset;
}
}
The FastAppResumeView.xaml page located in
the FastAppResume directory, in the WPUnleashed.Examples project,
allows you to try out the different Fast App Resume scenarios. A button
on the view is bound to an ICommand
named CreateShellTileCommand
that places a secondary tile on the Start Experience (see Listing 4). Resuming the app from the secondary tile causes the page navigation stack to be cleared.
LISTING 4. FastAppResumeViewModel
Class
public class FastAppResumeViewModel : ViewModelBase
{
string tileUrl = "/FastAppResume/DeepLinkedView.xaml?DeepLink=true";
public FastAppResumeViewModel()
{
createShellTileCommand = new DelegateCommand(
obj =>
{
if (!TileExists())
{
CreateTile();
}
},
obj => !TileExists());
}
bool TileExists()
{
ShellTile shellTile = ShellTile.ActiveTiles.FirstOrDefault(
x => x.NavigationUri.ToString().Contains(tileUrl));
return shellTile != null;
}
void CreateTile()
{
StandardTileData tileData = new StandardTileData
{
Title = "Unleashed Secondary",
};
/* Pin the tile to Start Experience.
* This causes a navigation event and deactivation of the app. */
ShellTile.Create(new Uri(tileUrl, UriKind.Relative), tileData);
}
readonly DelegateCommand createShellTileCommand;
public ICommand CreateShellTileCommand
{
get
{
return createShellTileCommand;
}
}
}
Fast App Resume
is an important new feature of the Windows Phone 8 OS that allows apps
to forgo the usual startup penalty when being relaunched. Apps
implementing this feature appear to start faster and give the
impression of having better performance than apps that do not.