Jeff Prosise's Blog

Real-World Tombstoning in Silverlight for Windows Phone, Part 1

One of the challenges that confronts every Windows phone developer is learning about tombstoning. As I briefly stated in an earlier article, tombstoning is Windows Phone 7’s way of allowing an application to restore itself to the same state it was in before it was interrupted. Although the operating system itself can run several applications at once, for performance reasons, applications written by you, me, and other non-OEMs are only allowed to run one at a time. If an application is running and it launches the phone’s Web browser to display a Web page, the app is tombstoned until the user presses the Back button to exit the browser. Once the app is reactivated (that is, if it’s reactivated – there’s no guarantee the user will press the Back button), it’s the app’s responsibility – not the operating system’s – to restore itself to its previous state before resuming execution. No app is required to support tombstoning, but users won’t care much for apps that lose their work just because they launched a browser or performed some other benign task. And such apps probably wouldn’t be approved for the Marketplace, anyway.

Of course, launching a browser isn’t the only reason apps get tombstoned. If the user presses the Start button – the big button in the bottom center of the phone with the Windows logo on it – while your app is running, the app gets tombstoned. If your app launches a PhoneCallTask, the app gets tombstoned. You’re never guaranteed that your app will be tombstoned, but you’re always guaranteed that it could be tombstoned. As such, you have to be prepared.

While conceptually easy, writing tombstoning code is littered with nuances that will suck up more than their share of development time. That’s why I decided to devote a series of articles to it. My intent isn’t to rehash the documentation; it’s to take you far beyond the documentation by building an application that supports tombstoning and letting you see in context the principles that guide the app’s design, development, and testing. Step by step, you’ll see the app come together. You’ll see the problems that tombstoning pose, and you’ll discover the meaning behind cryptic statements in the documentation such as “It is possible for an application to be deactivated without being tombstoned.” If you follow along with me til the end, I promise you’ll have a deeper understanding of tombstoning than 99% of your colleagues.

Without further ado, let’s get started.

In Part 1, we’re going to develop an app that lets the user edit photos. The app we’re building comes from a real project that I’m working on right now and trying to get ready for the Marketplace. The real app is a photo-extras app (I’ll explain what that means shortly) that allows the user to touch up photos by removing red eyes and such. Here it is with a photo of my kids showing:

screen1_thumb

Zooming in, you can see that the flash left the kids with some glaring red eyes (sorry, Amy):

screen2

Tap each red eye with a finger, and the red goes away. And yes, I’m using the Silverlight for Windows Phone Toolkit’s GestureListener class to simplify recognition of taps, pinches, and other gestures:

screen3_thumb

You can continue zooming, panning, and tapping until all the red eyes are fixed. Then you tap the Save icon in the application bar to save the edited photo:

screen4_thumb1

The saved photo appears in the phone’s Saved Pictures folder. From there, you can e-mail it, Facebook it, or do anything else with it that you normally do with pictures on your phone.

In the sample application that we’ll build together, I’ve removed all the nonessential features. For example, it doesn’t support panning, zooming, or even removing red eyes. It does, however, let you convert a color photo to grayscale, because I can make that happen with just a few lines of code and avoid detracting from the main point of the series – namely, incorporating tombstoning support in real-world applications.

So that you don’t have to do a lot of typing (or cutting and pasting), I’ve posted the project containing the sample app here. Unzip it, open it in Visual Studio, and take it for a test drive. You can run it on a phone or in the Windows phone emulator. Here it is in the emulator after I opened one of the emulator’s sample photos:

screen6

In this version, you can click the picture and convert all the pixels to grayscale:

screen7

You can even click the Save button in the application bar and save the edited photo to the Saved Pictures folder.

The sample app isn’t an ordinary, run-of-the-mill application; it’s a photo-extras application. This means that in addition to being launched the conventional way, it can be launched from the phone’s “extras” menu. To see what I mean, download the app to your phone (unfortunately, what you’re about to do doesn’t work in the emulator) and go to Pictures hub. Now select a picture – any picture – and click the ellipsis (the three dots) in the lower-right corner of the screen to display the photo menu:

image_thumb

Tap “extras…” at the bottom of the menu to display a list of photo-extras apps on your phone. Then tap “RealWorldTombstoningDemo” to launch the sample app with a picture already loaded.

The term “photo-extras app” simply means that the app appears in the “extras” menu for pictures. So how do you turn an ordinary application into a photo-extras application? Simple: include in your project a special file named Extras.xml containing the following XML:

 

<Extras>

    <PhotosExtrasApplication>

        <Enabled>true</Enabled>

    </PhotosExtrasApplication>

</Extras>

 

Extras.xml’s build action must be set to “Content” in Visual Studio’s Properties window, and “Copy to Output Directory” must be set to “Copy always” or “Copy if newer.” Otherwise, the operating system won’t be able to find the file and the app won’t be a photo-extras app after all.

The presence of Extras.xml in the application’s XAP file makes an app a photo-extras app and makes sure it appears in the “extras” menu. However, that alone doesn’t enable the app to automatically open the picture that was displayed when the app was launched from the “extras” menu. Here’s the logic that does:

 

if (NavigationContext.QueryString.ContainsKey("token"))

{

    // If the app was invoked through the Extras menu, grab the photo and display it

    MediaLibrary library = new MediaLibrary();

    Picture picture = library.GetPictureFromToken(NavigationContext.QueryString["token"]);

 

    BitmapImage bitmap = new BitmapImage();

    bitmap.SetSource(picture.GetImage());

    _bitmap = new WriteableBitmap(bitmap);

    Photo.Source = _bitmap;

}

 

When an application is invoked through the “extras” menu, Silverlight for Windows Phone launches the application and passes it a special token identifying the picture that was selected in a query-string parameter named “token.” The application retrieves the picture by passing the token to the XNA framework’s MediaLibrary.GetPictureFromToken method. (Notice that the sample project contains a reference to the Microsoft.Xna.Framework assembly, which houses the MediaLibrary class.) Silverlight can’t do much with an XNA Picture object, but you can wrap a BitmapImage object around the Stream returned by Picture.GetImage and wrap a WriteableBitmap object around the BitmapImage to convert it into a more useful form. The sample app displays the image by assigning the WriteableBitmap to the Source property of a XAML Image object.

For reference, I’ve reproduced most of the source code below. In addition to seeing the OnNavigatedTo method containing the code to load a photo if the app was launched from the “extras” menu, you’ll find an OnOpenButtonClicked method that executes when the user clicks the application bar’s Open button. That method launches a PhotoChooserTask allowing the user to select a picture from the Pictures library. PhotoChooser is one of several “launcher and chooser” classes in Silverlight for Windows Phone that are provided so apps can access native features of the phone such as the contacts list, the dialer, and the camera. PhotoChooserTask executes asynchronously. You launch it by calling Show, and when the user selects a photo, a Completed event fires. The Completed event handler (OnPhotoSelected) retrieves a WriteableBitmap representing the selected image from the ChosenPhoto property of the PhotoResult and displays it to the user.

 

// MainPage.xaml

<Grid x:Name="LayoutRoot" Background="Transparent">

 

  <!--ContentPanel - place additional content here-->

  <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">

    <Image x:Name="Photo" MouseLeftButtonDown="OnMouseLeftButtonDown" />

  </Grid>

</Grid>

 

<phone:PhoneApplicationPage.ApplicationBar>

  <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">

    <shell:ApplicationBarIconButton x:Name="OpenButton"

      IconUri="/Icons/appbar.folder.rest.png" Text="Open"

      Click="OnOpenButtonClicked"/>

    <shell:ApplicationBarIconButton x:Name="SaveButton"

      IconUri="/Icons/appbar.save.rest.png" Text="Save"

      Click="OnSaveButtonClicked" IsEnabled="False" />

  </shell:ApplicationBar>

</phone:PhoneApplicationPage.ApplicationBar>

 

// MainPage.xaml.cs

public partial class MainPage : PhoneApplicationPage

{

    private WriteableBitmap _bitmap;

    private PhotoChooserTask _task;

 

    // Constructor

    public MainPage()

    {

        InitializeComponent();

 

        // Create and initialize a PhotoChooserTask

        _task = new PhotoChooserTask();

        _task.Completed += new EventHandler<PhotoResult>(OnPhotoSelected);

    }

 

    protected override void OnNavigatedTo(NavigationEventArgs e)

    {

        Debug.WriteLine("OnNavigatedTo");

           

        if (NavigationContext.QueryString.ContainsKey("token"))

        {

            // If the app was invoked through the Extras menu, grab the photo and display it

            MediaLibrary library = new MediaLibrary();

            Picture picture =

                library.GetPictureFromToken(NavigationContext.QueryString["token"]);

 

            BitmapImage bitmap = new BitmapImage();

            bitmap.SetSource(picture.GetImage());

            _bitmap = new WriteableBitmap(bitmap);

            Photo.Source = _bitmap;

 

            // Disable the application bar's Save button

            (ApplicationBar.Buttons[1] as ApplicationBarIconButton).IsEnabled = false;

        }

    }

 

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)

    {

        Debug.WriteLine("OnMouseLeftButtonDown");

 

        if (_bitmap != null)

        {

            // Modify the image

            int width = _bitmap.PixelWidth;

            int height = _bitmap.PixelHeight;

 

            // Convert all pixels in the bitmap to grayscale

            for (int y = 0; y < height; y++)

            {

                for (int x = 0; x < width; x++)

                {

                    int index = (y * width) + x;

                    int argb = _bitmap.Pixels[index];

                    byte a = (byte)(argb >> 24);

                    byte r = (byte)((argb & 0x00FF0000) >> 16);

                    byte g = (byte)((argb & 0x0000FF00) >> 8);

                    byte b = (byte)(argb & 0x000000FF);

                    byte gray = (byte)((0.3 * r) + (0.59 * g) + (0.11 * b));

                    _bitmap.Pixels[index] = (a << 24) | (gray << 16) | (gray << 8) | gray;

                }

            }

 

            _bitmap.Invalidate();

               

            // Enable the application bar's Save button

            (ApplicationBar.Buttons[1] as ApplicationBarIconButton).IsEnabled = true;

        }

    }

 

    private void OnOpenButtonClicked(object sender, EventArgs e)

    {

        // Launch a photo chooser

        _task.Show();

    }

 

    private void OnPhotoSelected(object sender, PhotoResult e)

    {

        Debug.WriteLine("PhotoChooserTask.Completed");

           

        if (e.TaskResult == TaskResult.OK)

        {

            // Display the selected photo

            BitmapImage bitmap = new BitmapImage();

            bitmap.SetSource(e.ChosenPhoto);

            _bitmap = new WriteableBitmap(bitmap);

            Photo.Source = _bitmap;

 

            // Disable the application bar's Save button

            (ApplicationBar.Buttons[1] as ApplicationBarIconButton).IsEnabled = false;

        }

    }

 

    private void OnSaveButtonClicked(object sender, EventArgs e)

    {

        if (_bitmap != null)

        {

            using (MemoryStream stream = new MemoryStream

                (_bitmap.PixelWidth * _bitmap.PixelHeight * 4))

            {

                // Encode the WriteableBitmap in a MemoryStream

                _bitmap.SaveJpeg(stream, _bitmap.PixelWidth, _bitmap.PixelHeight, 0, 100);

 

                // Save the encoded image to the photo library

                MediaLibrary library = new MediaLibrary();

                library.SavePicture("SavedPicture.jpg", stream.ToArray());

            }

 

            // Disable the application bar's Save button

            (ApplicationBar.Buttons[1] as ApplicationBarIconButton).IsEnabled = false;

 

            // Let the user know that we're done

            MessageBox.Show("Picture saved");

        }

    }

}

 

There’s quite a bit of functionality here considering the rather modest amount of code. But as you probably guessed, this app isn’t finished. In fact, it isn’t nearly finished, because tombstoning will wreck it like a freight train. Let me demonstrate.

Launch the app in the emulator and perform the following steps:

  1. Click the Open button in the application bar and select a picture. The picture should appear on the screen.
  2. Click the Open button again, but this time, don’t select a picture. Instead, click the Back button. So far, so good. The picture you chose a moment ago should still be displayed.
  3. Click the Start button to return to the phone’s main screen.
  4. Now click the Back button to return to your app. The picture is gone.

Congratulations! You just became a victim of tombstoning. The Back button was supposed to take you back to the application, and the application was supposed to be in the same state it was in before. But it wasn’t, because the operating system tombstoned the app when you clicked the Start button. More precisely, the operating system terminated the app when you clicked the Start button. Poof. Killed. Finis. Because our code didn’t anticipate this, the brand new instance of the app that was started when you clicked the Back button looks and feels like a new instance; any data entered into the previous instance is gone.

To understand what’s happening here, let’s run the app again with the debugger attached. You may have noticed that I instrumented the code with calls to Debug.WriteLine. (I instrumented some of the code in App.xaml.cs, too.) Those calls will reveal what’s happening under the hood. So, do the following:

Step 1: Press F5 to start the app in the emulator with the debugger attached

The app should start up in the emulator. The following debug output appears in Visual Studio’s output window:

'taskhost.exe' (Managed): Loaded 'C:\Program Files\Microsoft Visual Studio 8\SmartDevices\SDK\CompactFramework\2.0\v1.0\WindowsCE\mscorlib.dll'
'taskhost.exe' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'taskhost.exe' (Managed): Loaded 'System.dll'
'taskhost.exe' (Managed): Loaded 'System.Windows.dll'
'taskhost.exe' (Managed): Loaded 'System.Core.dll'
'taskhost.exe' (Managed): Loaded 'System.Xml.dll'
'taskhost.exe' (Managed): Loaded '\Applications\Install\EC603EC3-7050-4576-A8E2-85CD4203492C\Install\RealWorldTombstoningDemo.dll', Symbols loaded.
'taskhost.exe' (Managed): Loaded 'Microsoft.Phone.dll'
'taskhost.exe' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
Launching
'taskhost.exe' (Managed): Loaded 'System.Runtime.Serialization.dll'
'taskhost.exe' (Managed): Loaded 'Microsoft.Xna.Framework.dll'
OnNavigatedTo

The first several lines show that the app has been launched (a new process created) and a bunch of assemblies have been loaded. Notice the statement that says “Launching.” That comes from App.xaml.cs and indicates that the app has fired a Launching event. Launching is one of four application lifecycle events fired by the PhoneApplicationService class; the others are Closing, Activated, and Deactivated. Visual Studio has already registered handlers for all four events in App.xaml.cs; all I did was add Debug.WriteLine statements to each one. Furthermore, you can see that the page’s OnNavigatedTo method has been called. OnNavigatedTo, you’ll recall, is where I put the code that opens a photo if the app is launched from the “extras” menu.

Step 2: Click the application bar’s Open button

A single statement appears in the output window:

Deactivated

The operating system just deactivated the app, and PhoneApplicationService fired a Deactivated event to let you know. Most of the time, a Deactivated event is followed by a process shutdown, but not this time. I’ll explain why in a moment.

Step 3: Select a photo

This should take you back to the app with the photo you selected displayed. Moreover, this appears in the output window:

Activated
PhotoChooserTask.Completed
OnNavigatedTo

The Activated event indicates that the app has been reactivated. It’s followed by a Completed event fired by the PhotoChooserTask, where we retrieve the photo you selected. Finally, OnNavigatedTo is called again, as it is every time a page is displayed in a phone application.

Step 4: Click the Open button again, but don’t select a photo

Once more the output window tells us that the app was deactivated:

Deactivated

Step 5: Click the Back button

Once more we’re apprised that the app has been reactivated:

Activated
PhotoChooserTask.Completed
OnNavigatedTo

Interestingly enough, the PhotoChooserTask.Completed event fired, too, but this time it contained no image, so we ignored it.

Step 6: Click the Start button

Something important just happened. Take a look at the output window:

Deactivated
The thread '<No Name>' (0xc1a0106) has exited with code 0 (0x0).
The thread '<No Name>' (0xc760112) has exited with code 0 (0x0).

Guess what? The process just ended. No wonder the picture disappeared when you tried this the first time and then hit the Back button!

Step 7: Click the Back button

Visual Studio now reconnects you to the app, but in order to do so, it has to restart the process. In the output window, you’ll see this:

'taskhost.exe' (Managed): Loaded 'C:\Program Files\Microsoft Visual Studio 8\SmartDevices\SDK\CompactFramework\2.0\v1.0\WindowsCE\mscorlib.dll'
'taskhost.exe' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'taskhost.exe' (Managed): Loaded 'System.dll'
'taskhost.exe' (Managed): Loaded 'System.Windows.dll'
'taskhost.exe' (Managed): Loaded 'System.Core.dll'
'taskhost.exe' (Managed): Loaded 'System.Xml.dll'
'taskhost.exe' (Managed): Loaded '\Applications\Install\EC603EC3-7050-4576-A8E2-85CD4203492C\Install\RealWorldTombstoningDemo.dll', Symbols loaded.
'taskhost.exe' (Managed): Loaded 'Microsoft.Phone.dll'
'taskhost.exe' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
Activated
'taskhost.exe' (Managed): Loaded 'System.Runtime.Serialization.dll'
'taskhost.exe' (Managed): Loaded 'Microsoft.Xna.Framework.dll'
OnNavigatedTo

The process has been started again. But notice that instead of firing a Launching event, your phone fired an Activated event.

Step 8: Click the Back button again

Clicking the Start button while your app is running deactivates the app, but clicking the Back button while your app is running closes it once and for all. Notice the debug output:

Closing
The thread '<No Name>' (0xd4d00ba) has exited with code 0 (0x0).
The thread '<No Name>' (0xdcb0116) has exited with code 0 (0x0).
The program '[213713150] taskhost.exe: Managed' has exited with code 0 (0x0).

The Closing event means you’re done. The user might choose to launch the app again, but as far as the operating system is concerned, there’s no need to try to convince the user that they’ve reconnected to a previous instance.

What Does It All Mean?

A lot of important things just happened and I want to make sure you understand them.

First and foremost: Apps are always deactivated when they’re switched away from. When an app is deactivated, a Deactivated event fires.

Most of the time, an app is shut down – its process terminated – when it’s deactivated. However, there are exceptions. For example, Execution Model Overview for Windows Phone in the WP7 documentation says “In order to improve performance around several common user scenarios, [some] Launchers and Choosers will typically not cause your application to be tombstoned.” Translated, this means that when you launch a PhotoChooserTask or one of a handful of other tasks, your app will be deactivated, but it probably won’t be terminated. The documentation says “will typically not” because under the hood, if the operating system’s resource manager decides it needs the memory the app consumes, it will tombstone the app (shut down the process).

The term “tombstoned” means “deactivated and terminated.” Remember that cryptic statement in the documentation I quoted earlier? “It is possible for an application to be deactivated without being tombstoned.” Photo choosers are an example of this. Launch a PhotoChooserTask and the app is deactivated but not terminated; therefore, the app wasn’t tombstoned. But remember that launching the same PhotoChooserTask under low-memory conditions could indeed cause tombstoning to occur. Also remember that deactivation without tombstoning is the exception rather than the rule. Most of the time, deactivation == tombstoning.

The fact that the app wasn’t tombstoned when you launched the PhotoChooserTask explains why the image you had loaded didn’t disappear when you clicked the Back button. The process was never shut down; when you clicked Back, the phone simply connected you to the still-running process, and no state was lost. Incidentally, this was a late change in the Windows Phone 7 operating system. If you tried the same experiment with some of the prerelease builds, you’d find that the app gets tombstoned when the PhotoChooserTask launches, and that the image is lost when you click the Back button.

Now, why did the image disappear when you clicked the Start button and then the Back button? Because clicking the Start button tombstoned your app. (Incidentally, clicking the Start button isn’t guaranteed to tombstone the app. If you click the Back button quickly enough – usually within a second or two of clicking the Start button – Windows Phone 7 may connect you right back to the app.) The process shut down, and because we didn’t do anything special to persist the state of the app until it was resurrected (activated), it was obvious to the user that a whole new app had been launched. We can’t prevent the app from being deactivated, but we can take advantage of page state, application state, and other persistence mechanisms to save the state of the app prior to deactivation and restore it upon reactivation. That’s the crux of writing tombstoning support into an app, and that’s exactly what we’ll do in Part 2.

On Feb 4 2011 2:00 PMBy jprosise With 6 Comments

Comments (6)

  1. Hi Jeff,
    Thanks for this wonderful series on tombstonig. It has cleared lot many hurdles and doubts for me.
    For some reason I can't see "extras...' options. Does this has to do with Mango version? I am not sure.
    Thanks.

  2. Anthony Wieser

    Be careful not to press your button too quickly, or your second call to _task.Show() will crash.

Leave a Comment