Обзор архитектуры. Жизненный цикл приложения с архитектурой WinRT. Особенности архитектуры Использование инструментария СLR, .net. Методы программирования
Поскольку все приложения WinRT ориентируют пользователя на работу с контентом, то нет необходимости держать приложение в памяти, когда пользователь начал работу с другим приложением. С другой стороны, пользователь может в любой момент попытаться вернуться в приложение, поэтому производить его запуск с нуля тоже плохо. В связи с этим, Windows 8 приостанавливает работу приложения всякий раз, когда пользователь переключается на какое-то другое занятие, и держит это приложение в памяти, пока есть возможность. Как только пользователь возвращается в приложение, Windows 8 может либо активировать существующий в памяти экземпляр, либо же, если приложение было удалено, вызвать его снова. Именно поэтому у разработчика возникает ряд задач.
- Как только приложение переходит в состояние ожидания, данные, критичные для его возобновления, необходимо сохранить.
- В случае возобновления работы приложения из памяти необходимо определиться, нужно ли обновлять интерфейс приложения (так как за время его "сна" многое могло измениться) и восстанавливать сетевые подключения. Ведь если приложение оставалось в режиме сна слишком долго, проще обновить весь интерфейс с нуля.
- В случае возобновления работы приложения необходимо считать все сохраненные данные и запустить приложение с того состояния (если это возможно), на котором пользователь покинул приложение, либо же перезапустить приложение в штатном режиме.
Приложение может быть приостановлено, если пользователь переключается на другую задачу или если Windows переводит компьютер в режим пониженного энергопотребления. Пока приложение приостановлено, оно продолжает находиться в памяти, поэтому пользователи могут быстро и надежно переключаться между приостановленными приложениями и возобновлять их работу. Когда приложение приостанавливается, а затем возобновляет работу, вам не нужно создавать дополнительный код для восстановления первоначального вида приложения.
Однако Windows может в любое время завершить работу приостановленного приложения, чтобы освободить память для других программ или для экономии энергии. Если приложение завершается, оно останавливает свою работу и выгружается из памяти.
Когда пользователь закрывает приложение нажатием клавиш Alt+F4 или жестом закрытия, приложение приостанавливается на 10 секунд и затем завершает работу.
Windows передает приложению сообщение о том, что оно будет приостановлено, но дополнительно не предупреждает о завершении работы приложения. Поэтому приложение должно обрабатывать событие приостановки, чтобы сохранить свое состояние и немедленно освободить собственные монопольные ресурсы и дескрипторы файлов.
Для успешного взаимодействия с пользователем необходимо восстанавливать первоначальный вид приложения, который был до приостановки. Это означает, что приложение должно сохранять любые введенные пользователем данные, измененные им параметры и т. д. Состояние приложения должно сохраняться при приостановке приложения на случай, если Windows завершит его. Только при таких условиях впоследствии можно восстановить состояние приложения.
В общем случае в приложении имеется два типа данных, которыми нужно управлять: данные приложения и данные сеанса.
Чтобы реализовать все вышеописанные механизмы, в классе Application (С#, C++) представлено несколько полезных событий. Давайте их рассмотрим.
- Activated - событие генерируется в случае успешного запуска приложения. Теоретически, именно здесь необходимо определять, как именно было запущенно приложение, нужно ли восстанавливать состояние и др. На практике переопределить это событие нельзя, и тут существуют другие механизмы.
- Suspending - это событие позволяет сохранить текущее состояние приложения. На это у приложения есть 5 секунд, после чего работа будет завершена в любом случае. Ничего сложного в рассматриваемом событии нет, достаточно определить обработчик и использовать локальное хранилище для сохранения данных.
- Resuming - событие, которое дает шанс разработчику восстановить внешние ресурсы в случае, если приложение было запущено из памяти. Очевидно, что во время "сна" приложения сетевые соединения были разрушены, да и контент мог измениться. Тут нужно учитывать, что обработчик этого события не запускается в интерфейсном потоке, следовательно, нужно использовать Dispatcher.
Вернемся к событию Activated. Дело в том, что приложение может быть запущено по многим причинам: для использования приложения как источника поиска; в результате передачи какого-то файла данных; вследствие активации через FilePicker и др. Именно поэтому, чтобы облегчить жизнь разработчику, класс Application предлагает несколько методов перегрузки. Примерами таких методов могут служить OnFileActivated, OnSearchActivated, OnShareTargetActivated и др. Иными словами, если запуск Вашего приложения связан с инициацией какого-то контракта, то нужно попробовать найти метод, соответствующий этому контракту. Если метод для контракта не найден, то можно воспользоваться методом OnActivated, который получает в качестве параметров тип контракта.
async protected override void OnActivated(IActivatedEventArgs args) { switch (args.Kind) { case ActivationKind.CameraSettings: . . . . break; case ActivationKind.ContactPicker: . . . . break; case ActivationKind.PrintTaskSettings: . . . . break; . . . . } base.OnActivated(args); }
Если же не интересуют контракты, то рекомендуется перегружать метод OnLanched (он уже есть у Вас в коде) и именно тут делать проверки на состояние, из которого было запущено приложение, а также принимать решения, какие данные нужно обновлять и какую страницу делать основной. Параметр метода OnLauched содержит одно полезное свойство - PreviousExecutionState, которое позволяет определить, из какого состояния было запущено приложение.
Для реализации переходов между различными состояниями жизненного цикла необходимо осуществлять хранение данных. У приложения должна быть возможность сохранять данные - настройки, сведения о состоянии и данные для будущей синхронизации. К тому же следует обеспечить комфортную работу пользователя на различных устройствах.
Данные, предназначенные для хранения, могут быть представлены и как набор переменных простых типов, и в виде файлов различной структуры. В связи с этим в Windows Runtime выделяют три способа хранения данных приложения.
- Локальные данные - все настройки сохраняются в реестре или внутри файлов, ассоциированных с конкретным пользователем.
- Роуминг данных - данные размещаются в облаке и могут использоваться приложением на различных устройствах.
- Временные данные - данные размещаются во временном хранилище и могут быть удалены в любой момент.
Рассмотрим детально локальное хранение данных и временные данные. Роуминг данных будет рассмотрен далее.
Хранение данных локально
Существует два локального хранения данных:
- Хранить простые данные внутри реестра;
- Хранить данные внутри файлов.
Разумеется, что поскольку приложения WinRT работают внутри собственной "песочницы", то прямого доступа к реестру или файловой системе у них нет. По этой причине Windows Runtime предлагает специальный набор классов, который позволяет хранить данные в выделенном разделе реестра или папке, ассоциированной с конкретным пользователем.
Представленные классы выполняют всю черновую работу. По большому счету, разработчик может и не догадываться, где хранятся данные. Доступны следующие классы.
- ApplicationDataContainer - представляет собой контейнер, куда можно сохранять данные простых типов или данные, построенные на базе.
- ApplicationDataCompositeValue. С каждым приложением ассоциируется контейнер по умолчанию, но можно и принудительно создавать вложенные контейнеры (аналог ключей и папок в реестре).
- ApplicationDataCompositeValue - этот класс позволяет собрать сложный тип данных на основании нескольких простых. Фактически, указанный механизм облегчает группировку данных простых типов. Подобное можно сделать и с помощью вложенных контейнеров, но рассматриваемый класс более прост в использовании.
Перечисленные выше классы находятся в пространстве имен Windows.Storage и могут быть использованы без особых затруднений. Например, если необходимо получить контейнер по умолчанию или создать новый именованный контейнер, то можно применить следующий код.
ApplicationDataContainer current = ApplicationData.Current.LocalSettings; ApplicationDataContainer named = current.CreateContainer( "myContainer", ApplicationDataCreateDisposition.Always);
Этот код получает ссылку на основной контейнер, ассоциированный с пользователем приложения, а затем создает вложенный контейнер. Имея ссылку на контейнер, можно приступить к сохранению простых данных.
При взаимодействии с реестром поддерживаются следующие типы данных: Boolean, Double, Int32, Int64, Single, String, UInt8, UInt32, UInt64.
Код ниже демонстрирует, как можно добавить новую пару ключ-значение в контейнер.
current.Values["myValue"] = 5; current.Containers["myContainer"].Values["mySecondValue"] = "Hello";
Как видите, в примере присутствуют простые индексаторы, которые позволяют легко создать пару или получить значение по известному ключу.
Наконец, если требуется создать в контейнере комплексное (композитное) значение, то можно воспользоваться следующим кодом.
ApplicationDataCompositeValue composite =new ApplicationDataCompositeValue(); composite["fi rstVal"] = 1; composite["secondVal"] = "Hello"; current.Values["compValue"] = composite;
Гораздо интереснее работать с файлами. Для их хранения приложению также выделяется отдельный контейнер, с которым можно взаимодействовать при помощи класса StorageFolder.
StorageFolder current = ApplicationData.Current.LocalFolder;
Созданный выше объект располагает множеством интересных методов.
- CreateFileAsync - создает файл.
- CreateFolderAsync - создает каталог.
- DeleteAsync - удаляет объект (файл или каталог).
- RenameAsync - позволяет выполнить переименование объекта.
- GetFileAsync - позволяет вернуть ссылку на файл с указанным именем (объект типа StorageFile).
- GetFilesAsync - возвращает список файлов, соответствующих запросу.
- GetFolderAsync - возвращает указанный в параметрах каталог.
- GetFoldersAsync - возвращает список каталогов, соответствующих запросу.
- OpenStreamForReadAsync - открывает файл для чтения, позволяет вернуть объект типа Stream, представленный в платформе .NET Framework уже давно. Этот и следующий метод можно использовать для стандартного для .NET взаимодействия с файлами.
- OpenStreamForWriteAsync - аналогичный предыдущему методу, но открывает файл для записи.
- OpenAsync - позволяет открыть файл и обойтись только механизмами Windows Runtime, предоставляя механизмы работы с файлом через интерфейсы.
Рассмотрим небольшой пример, демонстрирующий создание файла в корневом каталоге приложения.
public async void WriteFile() { StorageFolder current = ApplicationData.Current.LocalFolder; StorageFile fi le = await current.CreateFileAsync( "hello.txt", CreationCollisionOption.ReplaceExisting); IRandomAccessStream writeStream = await fi le.OpenAsync(FileAccessMode.ReadWrite); IOutputStream outputStream = writeStream.GetOutputStreamAt(0); DataWriter dataWriter = new DataWriter(outputStream); dataWriter.WriteString("hello"); await dataWriter.StoreAsync(); outputStream.FlushAsync(); }
Как видите, код не очень простой и требует регулярного обращения к операторам await, да и сам метод был объявлен с модификатором async. Зато мы воспользовались только возможностями WinRT, не прибегая к механизмам .NET, а чтобы не активировать механизмы работы с массивом байтов, применили вспомогательный класс DataWriter. Этот класс может использовать поток, чтобы облегчить процедуры чтения и записи в файл.
Процедура чтения данных из файла выглядит аналогично.
public async void ReadFile() { StorageFolder current = ApplicationData.Current.LocalFolder; StorageFile sampleFile = await current.GetFileAsync("hello.txt"); IRandomAccessStream readStream = await sampleFile.OpenAsync(FileAccessMode.Read); IInputStream inputStream = readStream.GetInputStreamAt(0); DataReader dataReader = new DataReader(inputStream); string myString = dataReader.ReadString((uint)readStream.Size); }
Краткие итоги
В лекции приведены этапы жизненного цикла приложений для Windows Store. Приводится описание платформ для их разработки.
Вопросы
- Какие этапы включает жизненный цикл приложения для Windows Store?
- Почему при приостановке приложения оно должно оставаться в памяти?
- Что происходит с приложением после окончания работы с ним?
- Какими типами данных необходимо управлять при возобновлении работы приложения?
- Какие платформы используются для разработки приложений для Windows Store?
- В чем особенности платформы .NET Framework?
- В чем особенности созданий приложения WinRT с помощью C# или Visual Basic?