The previous 2 posts discussed using the Page and Application State Dictionaries to restore state following the application being tombstoned, as well as some other potential uses for the Application State Dictionary. This final post in the series will cover the user of Isolated Storage from Windows Phone applications.
Isolated Storage Basics
Isolated Storage provides a virtual file system that allows an application (and in some cases applications) access to an (ahem) isolated area of persistent storage, usually disk-based. The precise location is hidden from applications and is not meant to be human-readable/accessible. The technique of using Isolated Storage to preserve state is one that is available across all flavors of .Net – there is support for Isolated Storage in the .Net Framework, in the Silverlight Runtime (both on the PC and the Mac), and in Silverlight for Windows Phone. While many of the mechanics of using Isolated Storage APIs are similar between the platforms, there are some distinctions in Windows Phone that make its support for Isolated Storage unique.
At its core, data stored in Isolated Storage is placed in a scoped area called a Store. Each runtime described above has access to a different collection of scopes for the stores, with .Net having access to the most (User, Domain, Assembly, Roaming, Machine, Application), and Silverlight having access to just two – Site and Application. The Site scope allows shared storage access to all Silverlight applications on the same web domain, and the Application scope is identified by each unique Silverlight XAP file.
In Silverlight for Windows Phone, the Silverlight applications are not hosted on any web site, so the only Isolated Storage scope available in phone applications is per Application. One important implication of this is that there is currently no way of sharing or exchanging data between applications via the phone file system. Having gone over the concept of tombstoning, there’s also an implication that the technique of using Local Connections is not available for inter-application communication (if only one application can be running at a time, there’s nobody to communicate with.) Taking this all one step further, the only way to communicate between different applications in Windows Phone is by using some form of internet-based storage.
The following code shows the basic procedure for writing data to Isolated Storage:
private void WriteData()
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
// Get the directories in the current Store
String[] directoryNames = store.GetDirectoryNames(“search pattern”);
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
// Get the directories in the current Store
String[] directoryNames = store.GetDirectoryNames(“search pattern”);
// Get the files in the given directory path
String[] fileNames = store.GetFileNames(“search pattern”);
//Maybe create one or more directories
store.CreateDirectory(“SomeNewDirectory”);
// Open a file stream (normal File.Open behavior)
using (IsolatedStorageFileStream fileStream = store.OpenFile(“file path”, FileMode.Create))
{
using (StreamWriter writer = new StreamWriter(fileStream))
{
writer.WriteLine(“Some Data”);
}
}
}
}
- Use the GetUserStoreForApplication call to get a reference to the application’s Isolated Storage file store
- Using the store reference, it is possible to obtain a list of directories and/or files within the current store, as well as to create files and directories
- Note that the process for creating files is the same as for regular files (specifying FileMode, etc.)
- The result of an opened file is an IsolatedStorageFileStream object, which inherits from the FileStream class.
- Once a reference to the IsolatedStorageFileStream object is available, code can be written against it as it can for pretty much any FileStream. The example above wraps the stream with a StreamWriter which exposes a WriteLine that can be used to write a line of text into the stream (and hence the underlying file.)
- Note that using blocks are being used to perform the necessary cleanup on Disposable objects throughout the process.
The following code shows the basic procedure for reading code from Isolated Storage:
private void ReadData()
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
if (store.FileExists(“file path”))
{
using (IsolatedStorageFileStream fileStream = store.OpenFile(“file path”, FileMode.Open))
{
using (StreamReader reader = new StreamReader(fileStream))
{
String lineOfText = String.Empty;
while (null != (lineOfText = reader.ReadLine()))
{
DoSomethingInterestingWithText(lineOfText);
}
}
}
}
}
}
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
if (store.FileExists(“file path”))
{
using (IsolatedStorageFileStream fileStream = store.OpenFile(“file path”, FileMode.Open))
{
using (StreamReader reader = new StreamReader(fileStream))
{
String lineOfText = String.Empty;
while (null != (lineOfText = reader.ReadLine()))
{
DoSomethingInterestingWithText(lineOfText);
}
}
}
}
}
}
- Get a reference to the application’s Isolated Storage file store
- Using the store reference, check to see fi the desired file exists
- If the file exists, open the file for reading, which returns an IsolatedStorageFileStream object, which inherits from the FileStream class.
- Once a reference to the IsolatedStorageFileStream object is available, code can be written against it as it can for pretty much any FileStream. The example above wraps the stream with a StreamWriter which exposes a ReadLine that can be used to read a line of text at a time from the stream (and hence the underlying file.)
Within the System.IO.IsolatedStorage namespace, there is a “helper class” that reduces some of the work required for routine Isolated Storage tasks. Much like the Application and Page State Dictionaries, the IsolatedStorageSettings class exposes a Dictionary of objects that uses string values for keys. The IsolatedStorageSettings class is accessible as a singleton through the IsolatedStorageSettings.ApplicationSettings property (note that “regular Silverlight” also exposes IsolatedStorageSettings.SiteSettings for site-scoped storage.)
The following code shows writing and reading from the ApplicationSettings dictionary:
private void WriteSettingsData()
{
IsolatedStorageSettings.ApplicationSettings[“key”] = value;
}
{
IsolatedStorageSettings.ApplicationSettings[“key”] = value;
}
private void ReadSettingsData()
{
if (IsolatedStorageSettings.ApplicationSettings.Contains(“key”))
{
value = IsolatedStorageSettings.ApplicationSettings[“key”];
}
}
{
if (IsolatedStorageSettings.ApplicationSettings.Contains(“key”))
{
value = IsolatedStorageSettings.ApplicationSettings[“key”];
}
}
Many possibilities are available for storing and retrieving data through Isolated Storage beyond simply reading and writing text using the StreamWriter and/or IsolatedStorageSettings. These range from using Xml to hold serialized objects through custom database implementations, including my Wintellect colleague Jeremy Likness’s
Sterling Object-Oriented database and the Codeplex implementation/port of Sqlite. Like most file systems, there are concerns regarding the quantity and size of items in any given folder, and there is a great discussion here on the performance behavior of Isolated Storage in Windows Phone under load.
Sterling Object-Oriented database and the Codeplex implementation/port of Sqlite. Like most file systems, there are concerns regarding the quantity and size of items in any given folder, and there is a great discussion here on the performance behavior of Isolated Storage in Windows Phone under load.
Capacity
Another important distinction between Isolated Storage for Silverlight and Isolated Storage for Silverlight for Windows Phone is that whereas there are user quotas in “regular” Silverlight (1 MB max for browser-based apps before the user must be asked if they would like to increase the quota, and 25 MB for out-of-browser apps), there are no storage quotas in place for Silverlight for Windows Phone applications. Storage space is limited by the amount of storage space available on the device itself.
Not only is there no quota for the amount of storage that any one application can consume, there is also no File Explorer or WinDirStat equivalent for the phone to determine how much space is being consumed by any one application. Although there’s no way to know which application may be consuming all of the available space on a device, if an application is chosen for deletion, when an application is uninstalled from the phone (locate the shortcut in the application list, hold down the application icon, and in the context menu that appears, choose uninstall) its isolated storage contents are deleted and the space they occupied is recovered.
Figure 1- Prompt When Uninstalling an Application
To determine the total amount of space remaining on disk, the IsolatedStorageFile instance offers an AvailableFreeSpace property. With “traditional Silverlight”, this returns the amount of space remaining in the current quota. Since Silverlight for Windows Phone does not have quotas, the AvailableFreeSpace property will return the amount of storage space remaining on the device.
In the following screenshots, an application was created that wrote a large amount of data to Isolated Storage. A second application was written to show the amount of drive space remaining. In the first screenshot, the storage application has not yet written its data to disk. In the second screenshot, it has written data to disk, as can be seen by the reduced amount of available free space remaining. Finally, in the third screenshot, the storage application has been uninstalled and its storage space has been reclaimed. (Note that the extra space appearing in the third screenshot is most likely due to the additional space received by the removal of the Silverlight XAP file in addition to the content in Isolated Storage.)
Figure 2- Before Saving Large File
Figure 3- After Saving Large File
Figure 4- After “Big File” Application is Uninstalled
Knowing this about uninstallation, the next logical question usually deals with updating. When an application is updated (a new version is placed in the Marketplace), the data in Isolated Storage is retained. It is up to the application developer(s) to handle issues related to the handling of versioning with any previous application versions’ contents in Isolated Storage.
Tombstoning
While Isolated Storage is the only one of the three state-preservation mechanisms discussed that can work across multiple launches of the same application (and hence beyond Tombstoning), it is certainly usable for Tombstoning scenarios, and is absolutely necessary in some cases, such as when the amount of data to be stored exceeds the limitations imposed by the other techniques. Because of the performance implications of Isolated Storage (discussed in the previous article in the series, ~30% to ~55% slower) and the possibility of larger amounts of data, there are important time limits that need to be discussed.
First of all, as stated here in the MSDN documentation, if an application takes more than 10 seconds to launch, the application may be terminated by the operating system. Furthermore, the Windows Phone Application Certification Requirements state that in order to pass the certification process, an application must render its first screen “within 5 seconds after launch” (5.2.1a), and must be responsive to user input “within 20 seconds after launch” (5.2.1b) Finally, an application must conclude the Activated and Deactivated events within 10 seconds. (5.2.3) As a result, it may be beneficial to consider breaking the content in Isolated Storage into smaller files that are accessed as needed (instead of big-load-up-front), background threads be used for loading large content at startup, and similar consideration be employed when saving data.
With these time considerations in mind, the key application lifetime events include:
- Launching – Raised when a new instance of the application is launched (and will not be raised if the application is being activated following tombstoning.)
- Closing – Raised when the application is terminated. In Silverlight applications, this only occurs when the user uses the Back button past the first page in the application’s BackStack (and will not be raised when the application is being tombstoned.)
- Deactivated – Raised when the user navigates away from the application (and in most cases, is tombstoned.)
- Activated – Raised when the user returns to an application after it was deactivated.
Because both Deactivated and Closing (under most circumstances) result in the application being terminated and in-memory state being destroyed, it is important to ensure that state is appropriately saved and restored in both circumstances. State information retrieved when an application is initially launched is not automatically preserved or restored. For that reason, it is not atypical to see the Launching/Activated and Closing/Deactivated event handlers call into a common pair of methods (such as RestoreState and SaveState, respectively) to deal with common content.
In Closing
In this series, the posts focused on mechanisms for preserving state in Windows Phone Silverlight applications. The key concepts presented included:
- Using the Page State Dictionary
- Using the Application State Dictionary
- Using Isolated Storage
I hope that this coverage has helped to illustrate the benefits and limitations of each.