Статьи

Основы использования локального хранилища на Windows Phone 8

Многие сценарии использования приложений в Windows Phone основаны не только на данных, извлекаемых через сторонние службы или API-интерфейсы, но также на хранении этих данных временно или постоянно на отдельных устройствах. Windows Phone 7 предлагает разработчикам возможность использовать изолированное хранилище   — выделенное хранилище, которое напрямую связано с данным приложением. Windows Phone 8 расширяет этот контент и предлагает дополнительные возможности.

В этой статье я собираюсь охватить пространство имен Windows.Storage, предоставляемое через API-интерфейсы WinPRT, и Microsoft.Phone.Storage, предоставляемое через стандартные API-интерфейсы .NET.

Хранение на устройстве

Начнем с ApplicationData . Этот класс позволяет вам получить доступ к локальному кешу данных приложения. В некоторой степени это что-то вроде изолированного хранилища — оно все еще связано с приложением. Как и в случае любого другого контента на основе приложения, он будет удален при удалении приложения, поэтому, если пользователь захочет сохранить что-либо, даже если приложение удалено, предложите некоторые дополнительные параметры хранения, например SkyDrive.

Локальный путь к папке Application Data может быть примерно таким на телефоне:

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

Где GUID — это уникальный идентификатор приложения, объявленный в WMAppManifest.xml . Если вы работали с приложениями Магазина Windows раньше, подход — хранение данных с помощью ApplicationDataContainer , может показаться вам знакомым. Однако есть определенные ограничения, о которых вам нужно знать. Например, такие полезности, как ApplicationData.Current.LocalSettings и ApplicationData.Current.RoamingFolder , не реализованы для Windows Phone.

Давайте перейдем к общим операциям, которые можно выполнить с папкой, и начнем с хранения контента. Способ сделать это очень похож на тот, который используется в приложениях Магазина Windows:

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);
    }
}

В этом примере фрагмента я храню строку в произвольном файле в локальной папке Application Data. Основными классами, которые вам необходимо использовать здесь, являются StorageFolder и StorageFile . Поскольку StorageFolder предоставляет вам доступ к корневому контейнеру и позволяет выполнять определенные операции, такие как получение файла или создание нового, StorageFile фактически позволяет получить доступ к содержимому файла. В приведенном выше примере файл открывается для записи, после чего вызывается WriteAsync со связанным массивом байтов данных — и это не обязательно должна быть строка. Это может быть изображение, музыкальный файл или видео — практически все, что вы хотели бы сохранить.

Когда вы создаете новый файл, вы можете при желании указать опцию столкновения через CreationCollisionOption . Обычной процедурой для большинства сценариев будет перезапись существующего файла. Также рекомендация в этом случае, если файловое хранилище открыто для пользователя и не используется внутри — предупредите пользователя, что некоторые данные могут быть потенциально потеряны, когда завершится новый процесс создания файла.

Процесс чтения можно легко обернуть в асинхронную функцию, например:

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 для доступа к контенту, на этот раз открывая основной поток для чтения вместо записи. Данные приложения всегда будут записываться на самом устройстве, а не во внешнем хранилище.

Внешнее хранилище

Но, как вы также знаете, Windows Phone 8 теперь поддерживает дополнительное хранилище, например SD-карты. Вот где классы, представленные через Microsoft.Phone.Storage, пригодятся. Список доступных в настоящее время внешних устройств хранения данных можно легко получить с помощью:

var storageAssets = await ExternalStorage.GetExternalStorageDevicesAsync();

Исходя из предположения, что доступна только одна внешняя карта, вы можете получить репрезентативный экземпляр с помощью LINQ (получение ExternalStorageDevice ):

ExternalStorageDevice item = storageAssets.FirstOrDefault();

Однако ограничение заключается в том, что для стороннего приложения доступ к внешнему хранилищу ограничен режимом только для чтения.

ПРИМЕЧАНИЕ. Несмотря на то, что вы можете без проблем получить текущий экземпляр для внешнего устройства хранения данных, если вы попытаетесь получить список доступных папок и файлов, вы получите исключение, если у вас не включена функция 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.