加载中

Many application usage scenarios on Windows Phone are not only relying on data that is being fetched through third-party services or APIs, but also on storing that data either temporarily or permanently on individual devices. Windows Phone 7 offered developers the ability to use the Isolated Storage  - a sandboxed storage allocation that is directly associated with a given application. Windows Phone 8 extends on top of that content and offers additional options.

Windows Phone上的许多应用的应用场景不仅仅依赖从第三方服务或者APIs接收过来的数据,还依赖于那些暂时或者永久性的存储在独立设备上的数据。Windows Phone 7提供开发者利用独立储存的能力。独立存储是直接绑定一个既有应用的、沙箱化的存储配置方案。Windows Phone 8扩展了这部分存储的大小,同时还提供了一些附加的选项。


In this article I am going to cover the Windows.Storage namespace, exposed through the WinPRT APIs, and Microsoft.Phone.Storage, exposed through the standard .NET APIs.

On-Device Storage

Let's begin with ApplicationData. This class allows you to access the local application data cache. To some extent, this is something like the Isolated Storage - it is still associated with the application. And as with any other app-based content, it will be removed when the application is uninstalled, so if there is anything that the user might want to keep even if the application is uninstalled, offer some additional storage options, such as SkyDrive.

在这篇文章里我将介绍 Windows.Storage 命名空间,WinPRT API,Microsoft.Phone.Storage和.NET提供的标准API。

设备存储

首先我们看看ApplicationData这个类允许你访问本地应用程序数据缓存。从某种程度上说,这很像 Isolated Storage- 它仍然是与应用程序相关联的。和其他应用的数据内容一样,在卸载应用程序时数据也同时删除。所以,如果用户希望在应用删除后还能查看到原来的数据,那么就需要考虑其他的存储方式了,比如 SkyDrive。


The local path for the Application Data folder might be something like this on the phone:

C:\Data\Users\DefApps\AppData\{5D93133B-3B39-4ABF-8D61-277D77B9F2AD}\Local

Where the GUID is the unique application identifier, declared in WMAppManifest.xml. If you've worked on Windows Store applications before, the approach - storing data with an ApplicationDataContainer , might seem familiar. However, there are specific limitations that you need to be aware of. For example, such goodies like ApplicationData.Current.LocalSettings and ApplicationData.Current.RoamingFolder are not implemented for Windows Phone.

在手机上应用程序的数据存放的文件夹地址类似如下:

C:\Data\Users\DefApps\AppData\{5D93133B-3B39-4ABF-8D61-277D77B9F2AD}\Local

GUID 是应用程序的唯一标识,它定义在WMAppManifest.xml文件中。如果你以前开发过Windows Store应用程序,你可能对ApplicationDataContainer这个更加熟悉。不过,有一些特殊的限制你需要注意一下,举个例子,比如像 ApplicationData.Current.LocalSettingsApplicationData.Current.RoamingFolder这样的好东西在Windows Phone并没有实现。

Let's move on to general operations that can be performed with the folder, and we'll begin with storing content. The way to do it is very similar to the one used in Windows Store applications:

async void WriteData(string fileName, string content)
{
    byte[] data = Encoding.UTF8.GetBytes(content);

    StorageFolder folder = ApplicationData.Current.LocalFolder;
    StorageFile file = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);

    using (Stream s = await file.OpenStreamForWriteAsync())
    {
        await s.WriteAsync(data, 0, data.Length);
    }
}

In this sample snippet I am storing a string in an arbitrary file in the local Application Data folder. The core classes that you need to leverage here are StorageFolder and StorageFile. As StorageFolder gives you the access to the root container and allows you to perform specific operations, such as getting the file or creating a new one, StorageFile actually allows you to access the file content. In the example above, the file is opened for writing, after which WriteAsync is called with an associated data byte array - and this doesn't have to be a string. It can be an image, a music file or a video - virtually anything you would like to store.

现在来看看关于文件夹的一般操作, 接下来我们就要开始储存数据了. 其实方法和Windows Store应用程序中的非常相似:

async void WriteData(string fileName, string content)
{
    byte[] data = Encoding.UTF8.GetBytes(content);

    StorageFolder folder = ApplicationData.Current.LocalFolder;
    StorageFile file = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);

    using (Stream s = await file.OpenStreamForWriteAsync())
    {
        await s.WriteAsync(data, 0, data.Length);
    }
}

上面的代码片段表示了在本地应用程序数据文件夹中的一个任意文件中储存一个字符串. 在这里你需要格外注意的核心类是StorageFolder 和 StorageFile. StorageFolder允许你访问根目录容器及执行指定操作, 如获取文件或新建文件, StorageFile实际上就是允许你访问文件容器. 上面的示例中, 在使用一个数据参数来调用WriteAsync后,文件就已经打开,可以写入了. 这个参数也不一定要是字符串的字节数组,它可以是图片,音乐文件或者视频文件 - 你想存什么就存什么.

When you are creating a new file, you can optionally specify a collision option, through CreationCollisionOption. The normal procedure for most scenarios would be overwriting the existing file. Also a recommendation in this case, if the file storage is exposed to the user and not used internally - warn the user that some data might be potentially lost when the new file creation process completes.

当你创建一个新文件,如果出现文件名冲突的情况(当前文件夹下存在同名文件),你可以通过CreationCollisionOption来选择如何进行处理。大多数情况下,程序会直接覆盖现有文件。对于这种情况,我有个建议,如果这块文件存储区域不是供程序内部使用,而且是对用户开放的,那么告知你的用户,当文件创建完后,一些数据可能会丢失。

The reading process can be easily wrapped in an async function like this:

async Task<string> ReadData(string fileName)
{
    byte[] data;
    
    StorageFolder folder = ApplicationData.Current.LocalFolder;

    StorageFile file = await folder.GetFileAsync(fileName);

    using (Stream s = await file.OpenStreamForReadAsync())
    {
        data = new byte[s.Length];
        await s.ReadAsync(data, 0, (int)s.Length);
    }

    return Encoding.UTF8.GetString(data, 0, data.Length);
}

Notice that, once again, I am relying on StorageFolder and StorageFile to access the content, this time opening the base stream for reading instead of writing. Application Data will always be written on the device itself and not in the external storage.

读取文件的代码可以很容易地写在一个异步函数中,就像下面这样:

async Task<string> ReadData(string fileName)
{
    byte[] data;
    
    StorageFolder folder = ApplicationData.Current.LocalFolder;

    StorageFile file = await folder.GetFileAsync(fileName);

    using (Stream s = await file.OpenStreamForReadAsync())
    {
        data = new byte[s.Length];
        await s.ReadAsync(data, 0, (int)s.Length);
    }

    return Encoding.UTF8.GetString(data, 0, data.Length);
}

值得再次注意的是,我通过StorageFolder 和 StorageFile 访问文件中的内容,我这次打开的是基本的输入流而不是输出流(也就是说,这次是读文件而不是写文件)。应用程序的数据只能存储在设备内部而不是外部存储设备上。

External Storage

But as you also know, Windows Phone 8 now supports additional storage, such as SD cards. Here is where the classes exposed via Microsoft.Phone.Storage come in handy. A list of currently available external storage devices can be easily obtained via:

var storageAssets = await ExternalStorage.GetExternalStorageDevicesAsync();

外部存储

但是,正如你所知道的,Windows Phone 8现在支持额外的存储器,例如SD卡。 这是这些类通过Microsoft.Phone.Storage展示它们派得上用场的地方。通过以下方式能够轻易的得到当前可用的外部存储设备列表:

var storageAssets = await ExternalStorage.GetExternalStorageDevicesAsync();


Based on the assumption that there is only one external card available, you can obtain the representative instance with the help of LINQ (getting an ExternalStorageDevice):

ExternalStorageDevice item = storageAssets.FirstOrDefault();

The limitation, however, is the fact that for a third-party application, access to the external storage is limited to the read-only mode.

假如只有一个外部存储卡,你可以通过 LINQ 来获取展示实例:
ExternalStorageDevice item = storageAssets.FirstOrDefault();
不过也有限制,第三方应用访问外部存储是只读模式。

NOTE: Although you can get the current instance for the external storage device with no problem, if you try to get the list of folders and files available, you will get an exception unless you have the ID_CAP_REMOVABLE_STORAGE capability enabled for the application.

A simple way to enumerate existing folders in the root would look like this:

var storageAssets = await ExternalStorage.GetExternalStorageDevicesAsync();

ExternalStorageDevice item = storageAssets.FirstOrDefault();
ExternalStorageFolder folder = item.RootFolder;
var _folderList = await folder.GetFoldersAsync();
foreach (var _folder in _folderList)
{
    Debug.WriteLine(_folder.Name);
}

注意:尽管你可以从外部存储设备上获取当前实例,而且不会报任何错,但是当你尝试获取文件夹和文件列表时,你会碰到一个程序异常,除非你的程序中启用了ID_CAP_REMOVABLE_STORAGE

你可以通过下面这种简单的方式获取目录下的文件夹列表:

var storageAssets = await ExternalStorage.GetExternalStorageDevicesAsync();

ExternalStorageDevice item = storageAssets.FirstOrDefault();
ExternalStorageFolder folder = item.RootFolder;
var _folderList = await folder.GetFoldersAsync();
foreach (var _folder in _folderList)
{
    Debug.WriteLine(_folder.Name);
}

And even though querying the folder list is easy, you will not get access to the files that are stored in those folders unless you explicitly associate your application with a file type. To do this, you have to open the WMAppManifest.xml file and add create the Extensions node, with the information related to the associated file type:

<Extensions>
  <FileTypeAssociation TaskID="_default" Name="PSD" NavUriFragment="fileToken=%s">
    <Logos>
      <Logo Size="small" IsRelative="true">Assets/Tiles/FlipCycleTileSmall.png</Logo>
      <Logo Size="medium" IsRelative="true">Assets/Tiles/FlipCycleTileMedium.png</Logo>
      <Logo Size="large" IsRelative="true">Assets/Tiles/FlipCycleTileLarge.png</Logo>
    </Logos>
    <SupportedFileTypes>
      <FileType ContentType="application/psd">.psd</FileType>
    </SupportedFileTypes>
  </FileTypeAssociation>
</Extensions>

You can then query for existing files as you would do with any other folder:

var storageAssets = await ExternalStorage.GetExternalStorageDevicesAsync();

ExternalStorageDevice item = storageAssets.FirstOrDefault();
ExternalStorageFolder folder = item.RootFolder;
var imagesFolder = await folder.GetFolderAsync("images");
var fileList = await imagesFolder.GetFilesAsync();

REMEMBER: You will only get the ExternalStorageFile  instances that have the extension listed in the Extensions node, in WMAppManifest.xml.

即使你能轻易地获取文件夹列表,但是你也无法取得文件夹下的文件,除非你明确得将应用程序和文件的文件类型关联在一起。要做到这一点,你必须打开WMAppManifest.xml文件,并添加一个Extensions节点,在节点中添加相关的文件类型信息:

<Extensions>
  <FileTypeAssociation TaskID="_default" Name="PSD" NavUriFragment="fileToken=%s">
    <Logos>
      <Logo Size="small" IsRelative="true">Assets/Tiles/FlipCycleTileSmall.png</Logo>
      <Logo Size="medium" IsRelative="true">Assets/Tiles/FlipCycleTileMedium.png</Logo>
      <Logo Size="large" IsRelative="true">Assets/Tiles/FlipCycleTileLarge.png</Logo>
    </Logos>
    <SupportedFileTypes>
      <FileType ContentType="application/psd">.psd</FileType>
    </SupportedFileTypes>
  </FileTypeAssociation>
</Extensions>

然后,你就可以获取文件下的文件了,其他文件夹下的文件也可以通过相同的方法获得:

var storageAssets = await ExternalStorage.GetExternalStorageDevicesAsync();

ExternalStorageDevice item = storageAssets.FirstOrDefault();
ExternalStorageFolder folder = item.RootFolder;
var imagesFolder = await folder.GetFolderAsync("images");
var fileList = await imagesFolder.GetFilesAsync();

记住:你只能获得那些扩展名位于WMAppManifest.xml文件的Extensions 节点中的ExternalStorageFile 实例。

返回顶部
顶部