Skip to main content
July 20, 2010
Windows Phone Developer Blog

Understanding the Windows Phone Application Execution Model, Tombstoning, Launcher and more… – Part 3.



In the first two Understanding the Windows Phone Application Execution Model, Tombstoning, Launcher and Choosers, and… Few More Things on the Way posts (part 1, and part 2) you learned about the different application lifecycle events – Launching, Deactivated, Closing, and Activated, and how they differ from one another. Based on that knowledge and this code, we’ll move forward.

Saving the transient state and navigating to the right page

As you will recall, the second page of our simple application includes two textboxes, one for allowing the user to enter a phone number that will be used to save new contact information, and a second textbox for allowing the user to enter an SMS message to be sent. This will become useful when we start playing with Launchers and Choosers in the context of tombstoning applications.

Let’s start by defining the problem. The easiest way is simply to run a small experiment:

  1. From Visual Studio, start your application.
  2. Navigate to the second page and enter some text in either or both textboxes.
  3. Press the Windows button; this will deactivate your application (see part 2 of this series).
  4. Press Back (once) to return to your application. This will result in a black screen on your emulator.
  5. Press F5, or use any other method to restart the Visual Studio debugging session to reactivate your application. (Again, if you are missing some context, please read part 2 of this series)
  6. At this stage, your application should be running and you should see the second page of the application. However, both textboxes are empty… the text that you entered before you deactivated your application is no longer there. It is gone!
image

From this little experiment you have learned that data entered into your application is not automatically saved when your application deactivates (remember, your application gets terminated). And since reactivating an application really means starting a new instance of the application with some tombstoned information, the page that you land on – in our case page 2, the DetailsPage, is a brand new instance of that page. This indicates that by default all the controls have no data, unless you load data into them.

The most important thing to remember about tombstoned applications is the simple fact that the user may NOT return to your application, and therefore your application may not be reactivated. If you anticipate this happening, you need to save to disk any data that you wish to restore at a later time. It is completely up to you and your responsibility as a developer to handle the saving and retrieving of data in your application

We identified two sets of data types that we want to store (copied from MSDN):

  • Persistent data – Data that is shared by all instances of an application. Persistent data is saved and loaded from isolated storage. Application settings are an example of persistent data that should be preserved between application executions.
  • Transient data –Data that describes the state of a single instance of an application. Transient data is stored in the State dictionary provided by the PhoneApplicationService class. A tombstoned application restores its transient state when it is activated.

We will address the persistent data and working with isolated storage in a future post. For now, let’s focus on managing the transient data and using the State dictionary.

One of the new classes in the SDK is PhoneApplicationService. The PhoneApplicationService class provides access to various conditions of the application’s lifetime. This includes management of the application’s idle behavior and management of the application’s state when it becomes active or inactive. This class plays a major role in the tombstoning game, since it exposes the Launching, Deactivate, Activated, and Closing events that have the corresponding methods (which you already saw) in the App.xaml.cs file. This class also includes a read-only State property of type IDictionary<string, object>. The importance of this dictionary is that it is persisted on your application’s behalf by the Windows Phone operating system when your application gets tombstoned. When the application reactivates, the objects that placed in the dictionary are returned. You don’t need to save these transient objects to disk as long as you expect your application to return from the tombstoned state. Therefore, if you intend to use the State dictionary, make sure you save only transient data in it– information that you don’t mind losing and that is local to the current application instance. In our example, we’ll save the phone number and SMS text message.

Please note that the objects you store in this dictionary have to be serializable, or else you will get an exception during the deactivating event about the inability of the operating system to sterilize or desterilize your object.

MSDN best practices recommend that you store transient page data in the State dictionary during the OnNavigatedFrom event, and load data during the NavigatedTo event.

I’ve update the application to save the phone number and the SMS message in the Store dictionary, and load it each time the page get navigated to. If a “completely” new instance (indicated by the “Lunching” event) of the application is started, the State dictionary is empty.

   1: protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)

   2: {

   3:   Util.Trace("***** in DetailsPage: OnNavigatedFrom ( " + DateTime.Now.Ticks + " *****)");

   4:  

   5:   //try to locate the phone number from previous save and simply override it

   6:   if (true == PhoneApplicationService.Current.State.ContainsKey(PhoneNumberKey))

   7:   {

   8:     //clear prev value

   9:     PhoneApplicationService.Current.State.Remove(PhoneNumberKey);

  10:   }

  11:   PhoneApplicationService.Current.State.Add(PhoneNumberKey, this.PhoneNumberTxt.Text);

  12:  

  13:   //try to locate the SMS Messagefrom previous save and simply override it

  14:   if (true == PhoneApplicationService.Current.State.ContainsKey(SmsMessageKey))

  15:   {

  16:     //clear prev value

  17:     PhoneApplicationService.Current.State.Remove(SmsMessageKey);

  18:   }

  19:   PhoneApplicationService.Current.State.Add(SmsMessageKey, this.MessageTxt.Text);

  20: }

  21:  

  22: // Step 2

  23: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)

  24: {

  25:   Util.Trace("***** in DetailsPage: OnNavigatedFrom ( " + DateTime.Now.Ticks + " *****)");

  26:  

  27:   //try to locate the phone number from previous run

  28:   if (true == PhoneApplicationService.Current.State.ContainsKey(PhoneNumberKey))

  29:   {

  30:     string s = PhoneApplicationService.Current.State[PhoneNumberKey] as string;

  31:     if (!String.IsNullOrEmpty(s))

  32:     {

  33:       Util.Trace("***** in DetailsPage: OnNavigatedTo: Found phone number ( " + 

  34:                                                     DateTime.Now.Ticks + " *****)");

  35:  

  36:       this.PhoneNumberTxt.Text = s;

  37:     }

  38:   }

  39:  

  40:   // Step 2

  41:   //try to locate the phone SMS MSG from previous run

  42:   if (true == PhoneApplicationService.Current.State.ContainsKey(SmsMessageKey))

  43:   {

  44:     Util.Trace("***** in DetailsPage: OnNavigatedTo: Found Sms Msg ( " + DateTime.Now.Ticks + " *****)");

  45:  

  46:     string s = PhoneApplicationService.Current.State[SmsMessageKey] as string;

  47:     if (!String.IsNullOrEmpty(s))

  48:     {

  49:       this.MessageTxt.Text = s;

  50:     }

  51:   }

  52: }

!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>

Note that with the following changes, the information in page 2 is stored between page navigation. That is when you press the Back button in page 2, the application navigates back to page 1. If you click the “Navigate to next page” from page 1, your application navigates to page 2. The OnNavigateTo (on page 2) event is raised, and with the above code the control will be loaded with data. This is important because if you take a closer look at the trace in the Output windows in Visual Studio you will find that the each time you navigate to page 2, its constructor is executed. This indicates that each time you press the Back button from page 2, that page is destroyed, and therefore each time you navigate back from page 1 to page 2, the page is recreated. To observe this behavior for yourself, simply navigate back and forth between the two pages, and note that the page 2 CTOR is called each time you navigate from page 1 to page 2. If you comment out the OnNavigatedFrom method in your application, and navigate between the two pages, you will see that the information on the second page is not saved.

image

Now tombstone the application to see for yourself that the phone number and SMS message are being stored, not just between page navigations, but also after leaving the application and returning to it. Simply navigate to page 2, enter a phone number and some text, and press the Windows button to tombstone the application. You should see the deactivated trace and then the application terminated. Press the Back button and don’t forget to press F5 in Visual Studio to restart the debugging session. Your application will return from its tombstoned state and you will see the launching event, and then the DetailsPage (page 2) constructor. Next you will see the OnNavigatedTo trace and, if everything works, you will see the “Found phone number” and “Found Sms Msg” lines in the trace (and in the emulator), as shown in the next image.

image

All this is nice, and I hope it explains the way things work with tombstoning Windows Phone applications. But, now it is time to step up our game and show some cool things in action.

First, I’ve added a helper Logger class that logs all the traces and displays them in the MainPage (first page) textbox. The goal of the Logger is to prove to you without a doubt that your application gets terminated and that the State dictionary really works. The logger will also allow you to run your application from the emulator, not in debug mode, and still get some information back through the traces shown in the log textbox.

Logger class

The Logger class includes a very simple (and most probably not that thread-safe) implementation of a Singleton. The main reason this class is a singleton is to show that even a singleton class is removed from memory when your application is terminated, as well as the order of events and the loading of data between the different events. This class has a string member, which is the log, and a DateTime member that saves the creation time of the Logger object. With the Logger class you can add new lines to the log, and get the entire log. This is good for debugging, which is exactly what the Logger is for. Each time you use the Util Class to add a trace, you also add it to the Logger.

Usually a singleton implementation doesn’t have a public constructor. However, you are going to save this class in the State dictionary, which means that the Logger class must be serializable and therefore must have a public default (empty) constructor. Otherwise you will get an exception when trying to deactivate or activate your application.

To “view” the log, I’ve added the OnNavigatedTo method to the MainPage.xaml.cl and loaded text in the log to the logTextBox control on the main page. Therefore each time you navigate to the main page of the application you will see the log file printed. Doing this enables you to view the trace in your application without the need to debug your application in VS (and thanks to Jaime Rodriguez for the tip). Let’s try it.

  1. First you need to deploy you application to the emulator. You can do this by right clicking on your project in the Visual Studio Solution Explorer panel, or from the Build menu. Simply click Deploy.
  2. In the emulator, navigate to the applications list by clicking on the white arrow on the top right corner of the phone.
  3. You should see the applications list, which is rather short. From the applications list pick your application. If you are using the code from this post, the application name is LWP.AppLifeCycle
  4. The application launches, and you see the trace in the MainPage textbox. You may want to change the phone setting (by clicking the wrench button) to set the zoom level of the emulator to 100%. The text in the log textbox is small to be able to show as much log history as possible without scrolling.
  5. In the log textbox you should see the trace from creating the application and the constructor of the main page.
  6. Click the Next Page button.
  7. On the second page, enter a phone number or some text to the SMS textbox.
  8. Then deactivate your application simply by clicking the Windows button. In the emulator, you should see the Start screen.
  9. Click the Back button to return to your application. This will reactivate your application and restore your application to the last page you viewed, that is the second page, and, if all goes well, you should also see the information you entered in step 7.
  10. On the second page, click the Back button to return to the first page. Take a look at the trace found in the log textbox. You should see something that looks the following image:
image

The interesting segment of this trace is the information between the dashed lines, and the following code snippet for which the Application Activated method is responsible.

 

   1: if (true == PhoneApplicationService.Current.State.ContainsKey(LoggerKey))

   2: {

   3:     Logger logger = (PhoneApplicationService.Current.State[LoggerKey] as Logger);

   4:     long timeDef = Logger.Instance.CreationTime.Ticks - logger.CreationTime.Ticks;

   5:     Util.Trace("-------------------------------------n--> Time difference between Loggers = " 

   6:                 + timeDef

   7:                 + "n" + logger.GetLog() 

   8:                 + "n-------------------------------------");

   9:     }

!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>!–crlf–>

 

In this code, you can see that we are looking for the Logger object in the State dictionary. The object was put into the State dictionary in the deactivated event. Assuming the logger object is found, we are creating a temporary object called logger (note this is not the actual logger object that we are using in the current application). Next we compare the creation time of the Logger from the State to the singleton Logger instance of the “new” application that we just activated. As you can see in the trace, there is a difference in time; the logger from the State dictionary that we saved when we last deactivated the application is older than the new logger. From the code you can see that we can’t really initiate the older logger; its creation time and log are restored and printed to the textbox. So everything between the dashed lines is actually the previous run of the application up to the deactivated event.

The first line after the dashed line is shows the constructor of the second page, as expected, and it shows the “new” application that got reactivated and returned to the second page, from which we had deactivated the application.

To summarize

Now you have seen all four events in action: Launching, Deactivated, Activated, and Closing. I hope you understand that when your application is not running, it is terminated, and any data you didn’t saved is gone. Then when you are return to the application, with either an Activated event or a Launching event, you get a NEW instance of the application (our little singleton experience proved that).

The State dictionary can save transient data between Deactivated and Activated events and will turn out to be a very useful tool in the next post where we’ll talk about choosers and launchers.