Multitasking or Tombstoning and Isolated Storage in Windows Phone 7


This chapter is taken from book "Programming Windows Phone 7" by Charles Petzold published by Microsoft press. http://www.charlespetzold.com/phone/index.html

We want our phones to be much like our other computers. We want to have a lot of applications available. We want to start up a particular application as soon as we conceive a need for it. While that application is running, we want it to be as fast as possible and have access to unlimited resources. But we want this application to coexist with other running applications because we want to be able to jump among multiple applications running on the machine.

Windows Phone 7 manages multiple active applications by implementing a stack. In a sense, this application stack extends the page stack within a single Silverlight program. You can think of the phone as an old-fashioned web browser with no tab feature and no Forward button. But it does have a Back button and it also has a Start button, which brings you to the Start screen and allows you to launch a new program.

Page State

The SilverlightFlawedTombstoning project is a simple Silverlight program with just one page. The program responds to taps on the screen by changing the background of ContentGrid to a random color, and displaying the total number of taps in its page title. Everything of interest happens in the code-behind file:

namespace SilverlightFlawedTombstoning
{
    public partial class MainPage : PhoneApplicationPage
    {
        Random rand = new Random();
        int numTaps = 0;
        public MainPage()
        {
            InitializeComponent();
            UpdatePageTitle(numTaps);
        }
        protected override void OnManipulationStarted(ManipulationStartedEventArgs args)
        {
            ContentPanel.Background = new SolidColorBrush(Color.FromArgb(255, (byte)rand.Next(256),
                                                        (byte)rand.Next(256),
                                                        (byte)rand.Next(256)));
            UpdatePageTitle(++numTaps); 
            args.Complete();
            base.OnManipulationStarted(args);
        }
        void UpdatePageTitle(int numTaps)
        {
            PageTitle.Text = String.Format("{0} taps total", numTaps);
        }
    }
}

The little UpdatePageTitle method is called from both the program's constructor (where it always results in displaying a value of 0) and from the OnManipulationStarted override.

Build and deploy the program to the phone or phone emulator by pressing F5 (or selecting Start Debugging from the Debug menu). Arrange Visual Studio so you can see the Output window. When the program starts up, tap the screen several times to change the color and bump up the tap count. Now press the phone's Start button. You can see from Visual Studio that two threads in the program end and the program has terminated, but to the phone the program has actually been deactivated and tombstoned.

Now press the Back button to return to the program. You'll see a blank screen with the word "Resuming…" and the Output window in Visual Studio shows libraries being loaded. That's the program coming back to life.

Although Windows Phone 7 leaves much of the responsibility for restoring a tombstoned application to the program itself, it will cause the correct page to be loaded on activation, so it's possible that a page-oriented Silverlight program that saves and restores page state data using the State property of PhoneApplicationSerivce class during OnNavigatedTo and OnNavigatedFrom will need no special processing for tombstoning. The phone operating system preserves this State property during the time a program is deactivated and tombstoned, but gets rid of it when the program closes and is terminated for real.

The code-behind file for SilverlightBetterTombstoning includes a using directive for Microsoft.Phone.Shell and uses this State dictionary. Here's the complete class:

namespace
SilverlightBetterTombstoning
{
    public partial class MainPage : PhoneApplicationPage
    {
        Random rand = new Random();
        int numTaps = 0;
        PhoneApplicationService appService = PhoneApplicationService.Current; 
        public MainPage()
        {
            InitializeComponent();
            UpdatePageTitle(numTaps);
        } 
        protected override void OnManipulationStarted(ManipulationStartedEventArgs args)
        {
            ContentPanel.Background = new SolidColorBrush(Color.FromArgb(255, (byte)rand.Next(256),
                                                        (byte)rand.Next(256),
                                                        (byte)rand.Next(256)));
            UpdatePageTitle(++numTaps); 
            args.Complete();
            base.OnManipulationStarted(args);
        }
        void UpdatePageTitle(int numTaps)
        {
            PageTitle.Text = String.Format("{0} taps total", numTaps);
        }
        protected override void OnNavigatedFrom(NavigationEventArgs args)
        {
            appService.State["numTaps"] = numTaps;
            if (ContentPanel.Background is SolidColorBrush)
            {
                appService.State["backgroundColor"] = (ContentPanel.Background as SolidColorBrush).Color;            }
            base.OnNavigatedFrom(args);
        }
        protected override void OnNavigatedTo(NavigationEventArgs args)
        {
            // Load numTaps
            if (appService.State.ContainsKey("numTaps"))
            {
                numTaps = (int)appService.State["numTaps"];
                UpdatePageTitle(numTaps);
            }
            // Load background color
            object obj;
            if (appService.State.TryGetValue("backgroundColor", out obj))
                ContentPanel.Background = new SolidColorBrush((Color)obj); 
            base.OnNavigatedTo(args);
        }
    }
}

Notice the appService field set to PhoneApplicationService.Current. That's just for convenience for accessing the State property. You can use the long PhoneApplicationService.Current.State instead if you prefer.

Isolated Storage

Every program installed on Windows Phone 7 has access to its own area of permanent disk storage referred to as isolated storage, which the program can access using classes in the System.IO.IsolatedStorage namespace. Whole files can be read and written to in isolated storage, and I'll show you how to do that in the program that concludes this chapter. For the program that following I'm going to focus instead on a special use of isolated storage for storing application settings. The IsolatedStorageSettings class exists specifically for this purpose.

For the SilverlightIsolatedStorage program, I decided that the number of taps should continue to be treated as transient data-part of the state of the page. But the background color should be an application setting and shared among all instances.

App.xaml.cs has empty event handlers for all the PhoneApplicationService events. I gave each handler a body consisting of a single method call and the LoadSettings and SaveSettings methods. Both methods obtain an IsolatedStorageSettings object. Like the State property of PhoneApplicationService, the IsolatedStorageSettings object is a dictionary. One method in the program loads (and the other saves) the Color property of the BackgroundBrush property:

public
partial class App : Application
{
    // Application settings
    public Brush BackgroundBrush { set; get; }
    // Easy access to the root frame
    public PhoneApplicationFrame RootFrame { get; private set; }
    // Constructor
    public App()
    {
        // Global handler for uncaught exceptions.
        // Note that exceptions thrown by ApplicationBarItem.Click will not get caught here.
        UnhandledException += Application_UnhandledException;
        // Standard Silverlight initialization
        InitializeComponent();
        // Phone-specific initialization
        InitializePhoneApplication();
    }
    // Code to execute when the application is launching (eg, from Start)
    // This code will not execute when the application is reactivated
    private void Application_Launching(object sender, LaunchingEventArgs e)
    {
        LoadSettings();
    }
    // 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)
    {
        LoadSettings();
    }
    // 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)
    {
        SaveSettings();
    }
    // Code to execute when the application is closing (eg, user hit Back)
    // This code will not execute when the application is deactivated
    private void Application_Closing(object sender, ClosingEventArgs e)
    {
        SaveSettings();
    }
    void LoadSettings()
    {
        IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
        Color clr;
        if (settings.TryGetValue<Color>("backgroundColor", out clr))
            BackgroundBrush =
new SolidColorBrush(clr);
    }
    void SaveSettings()
    {
        IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
        if (BackgroundBrush is SolidColorBrush)
        {
            settings[
"backgroundColor"] = (BackgroundBrush as SolidColorBrush).Color;
            settings.Save();
        }
    }
}

And finally the new MainPage.xaml.cs file. This file-and any other class in the program-can get access to the App object using the static Application.Current property and casting it to an App. The constructor of MainPage obtains the BackgroundBrush property from the App class, and the OnManipulationStarted method sets that BackgroundBrush property.

namespace
SilverlightIsolatedStorage
{
    public partial class MainPage : PhoneApplicationPage
    {
        Random rand = new Random();
        int numTaps = 0;
        PhoneApplicationService appService = PhoneApplicationService.Current; 
        public MainPage()
        {
            InitializeComponent();
            UpdatePageTitle(numTaps); 
            // Access App class for isolated storage setting
            Brush brush = (Application.Current as App).BackgroundBrush; 
            if (brush != null)
                ContentPanel.Background = brush;
        }
        protected override void OnManipulationStarted(ManipulationStartedEventArgs args)
        {
            SolidColorBrush brush =
                new SolidColorBrush(Color.FromArgb(255, (byte)rand.Next(256),
                                                        (byte)rand.Next(256),
                                                        (byte)rand.Next(256)));
            ContentPanel.Background = brush;
            // Save to App class for isolated storage setting
            (Application.Current as App).BackgroundBrush = brush;
            UpdatePageTitle(++numTaps); 
            args.Complete();
            base.OnManipulationStarted(args);
        }
        void UpdatePageTitle(int numTaps)
        {
            PageTitle.Text = String.Format("{0} taps total", numTaps);
        }
        protected override void OnNavigatedFrom(NavigationEventArgs args)
        {
            appService.State["numTaps"] = numTaps; 
            base.OnNavigatedFrom(args);
        }
        protected override void OnNavigatedTo(NavigationEventArgs args)
        {
            // Load numTaps
            if (appService.State.ContainsKey("numTaps"))
            {
                numTaps = (int)appService.State["numTaps"];
                UpdatePageTitle(numTaps);
            }
        }
    }
}

Xna Tombstoning and Settings

XNA applications aren't normally built around pages like Silverlight applications. If you wanted, however, you could certainly implement your own page-like structure within an XNA program. You'll recall that the state of the phone's Back button is checked during every call to the standard Update override. You can use this logic for navigational purposes as well as for terminating the program. But that's something I'll let you work out on your own.

I gave the XnaTombstoning project a dedicated Settings class that uses the more generalized features of isolated storage that involve real files rather than just simple settings. You'll need a reference to System.Xml.Serialization library for this class as well using directives for the System.IO, System.IO.IsolatedStorage, and System.Xml.Serialization namespaces.

namespace XnaTombstoning
{
    public class Settings
    {
        const string filename = "settings.xml"
        // Application settings
        public Color BackgroundColor { set; get; } 
        public Settings()
        {
            BackgroundColor = Color.Navy;
        } 
        public void Save()
        {
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
            IsolatedStorageFileStream stream = storage.CreateFile(filename);
            XmlSerializer xml = new XmlSerializer(GetType());
            xml.Serialize(stream, this);
            stream.Close();
            stream.Dispose();
        } 
        public static Settings Load()
        {
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
            Settings settings;
            if (storage.FileExists(filename))
            {
                IsolatedStorageFileStream stream = storage.OpenFile("settings.xml", FileMode.Open);
                XmlSerializer xml = new XmlSerializer(typeof(Settings));
                settings = xml.Deserialize(stream) as Settings;
                stream.Close();
                stream.Dispose();
            }
            else
            {
                settings = new Settings();
            }
            return settings;
        }
    }
}

The remainder of the XnaTombstoning project lets you tap the screen and responds by displaying a new random background color and a count of the number of taps. The background color is treated as an application setting (as is evident by its inclusion in the Settings class) and the number of taps is a transient setting.

Here's an excerpt of the Game1 class showing the fields, constructor, and PhoneApplicationService events:

namespace XnaTombstoning
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
 
        Settings settings;
        SpriteFont segoe14;
        Viewport viewport;
        Random rand = new Random();
        StringBuilder text = new StringBuilder();
        Vector2 position;
        int numTaps = 0;
 
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
 
            // Frame rate is 30 fps by default for Windows Phone.
            TargetElapsedTime = TimeSpan.FromTicks(333333);
 
            TouchPanel.EnabledGestures = GestureType.Tap;
 
            PhoneApplicationService appService = PhoneApplicationService.Current;
            appService.Launching += OnAppServiceLaunching;
            appService.Activated += OnAppServiceActivated;
            appService.Deactivated += OnAppServiceDeactivated;
            appService.Closing += OnAppServiceClosing;
        } 
        protected override void Initialize()
        {
            base.Initialize();
        }
 
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            segoe14 = this.Content.Load<SpriteFont>("Segoe14");
            viewport = this.GraphicsDevice.Viewport;
        } 
        protected override void UnloadContent()
        {
        }
 
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit(); 
            while (TouchPanel.IsGestureAvailable)
                if (TouchPanel.ReadGesture().GestureType == GestureType.Tap)
                {
                    numTaps++;
                    settings.BackgroundColor =  new Color((byte)rand.Next(255),
                                                          (byte)rand.Next(255),
                                                          (byte)rand.Next(255));
                } 
            text.Remove(0, text.Length);
            text.AppendFormat("{0} taps total", numTaps);
            Vector2 textSize = segoe14.MeasureString(text.ToString());
            position = new Vector2((viewport.Width - textSize.X) / 2,
                                   (viewport.Height - textSize.Y) / 2);
            base.Update(gameTime);
        }
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(settings.BackgroundColor);
            spriteBatch.Begin();
            spriteBatch.DrawString(segoe14, text, position, Color.White);
            spriteBatch.End();
            base.Draw(gameTime);
        } 
        protected override void OnActivated(object sender, EventArgs args)
        {
            if (PhoneApplicationService.Current.State.ContainsKey("numTaps"))
                numTaps = (int)PhoneApplicationService.Current.State["numTaps"]; 
            base.OnActivated(sender, args);
        } 
        protected override void OnDeactivated(object sender, EventArgs args)
        {
            PhoneApplicationService.Current.State["numTaps"] = numTaps;
            base.OnDeactivated(sender, args);
        } 
        void OnAppServiceLaunching(object sender, LaunchingEventArgs args)
        {
            settings = Settings.Load();
        }
        void OnAppServiceActivated(object sender, ActivatedEventArgs args)
        {
            settings = Settings.Load();
        }
        void OnAppServiceDeactivated(object sender, DeactivatedEventArgs args)
        {
            settings.Save();
        }
        void OnAppServiceClosing(object sender, ClosingEventArgs args)
        {
            settings.Save();
        }
    }
}

The program will get a call to OnActivated about the same time the Launching and Activated events are fired, and a call to OnDeactivated about the same time the Deactivated and Closing events are fired. The differentiation is more conceptual in that OnActivated and OnDeactivated are associated with the Game instance, so they should be used for properties associated with the game rather than overall application settings.

Summary

I hope this article helps you to learn Multitasking or Tombstoning in Windows Phone 7 in Windows Phone 7.


Similar Articles