Жизненный цикл приложения в WinRT

65

Программа PrimitivePad, созданная в предыдущей статье, также имеет неочевидный недостаток, с которым тоже нужно что-то сделать. Если запустить программу Блокнот на обычном настольном компьютере Windows, ввести текст, а потом попытаться закрыть программу (кнопкой закрытия в правом верхнем углу, клавишами Alt+F4, командой Файл --> Выход или завершением сеанса Windows), Блокнот выведет окно сообщения с предложением сохранить изменения. Вы можете выбрать между сохранением, потерей изменений или отменой завершения.

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

Вы хотите, чтобы ваши Windows-приложения предлагали сохранять данные при переходе в спящий режим? Нет, не хотите. Переводя компьютер в спящий режим (или просто отворачиваясь от экрана), вы ясно показываете, что не собираетесь продолжать работу с компьютером в ближайшее время.

Но тогда возникает проблема: что делать, если планшет, лежащий на кофейном столике, определит, что заряд батареи недостаточен даже для работы в спящем режиме, и решит отключиться? Или другая ситуация: вы используете компьютер, и Windows не хватает памяти. Чтобы освободить ее, система может завершит приложения, которые не использовались в течение некоторого времени. Вряд ли вам как пользователю захочется получать оповещения об этом событии.

По этим причинам воспитанное приложение для Windows 8 сохраняет информацию, чтобы работа пользователя проходила непрерывно независимо от того, завершается оно или нет. Если приложение содержит несохраненные данные, которые пользователю не хотелось бы терять и это приложение завершается, то при следующем запуске оно должно отобразить предыдущие данные. (Разумеется, для некоторых приложений эта функциональность менее важна, чем для других. Например, для калькулятора потеря данных вряд ли кого-нибудь огорчит, а для электронной таблицы последствия могут быть очень серьезными.)

Но ведь проблема легко решается? Программисту нужно знать совсем немного - какое событие инициируется перед завершением приложения? Вы используете это событие для сохранения любых несохраненных данных в локальном хранилище приложения, а потом восстанавливаете эти данные при следующем запуске программы. Остается одна проблема: такого события не существует.

Правда, есть событие, сообщающее о приостановке приложения. Приложения всегда приостанавливаются перед завершением (кроме аварийного завершения), но приостановка не всегда приводит к завершению. Есть другое событие, сообщающее о продолжении работы приложения после приостановки.

Приложение приостанавливается тогда, когда оно перестает работать в активном режиме - то есть когда вы вызываете начальный экран Windows или проводите пальцем от левого края, чтобы сделать активной другую программу. Приложение также приостанавливается, когда вы нажимаете клавиши Alt+F4 для его завершения или переводите свой компьютер в спящий режим. Во всех перечисленных случаях приостановке предшествует примерно 10-секундная задержка; в это время Windows (и приложения) должны подготовиться к возможному завершению.

После того как программа будет приостановлена, она либо завершается, либо продолжает свое выполнение. Между приостановкой и завершением/продолжением может пройти много времени. Так как события завершения приложения не существует, приложение должно использовать приостановку для сохранения всех данных, необходимых для продолжения работы - даже если приостановка не приведет к завершению. (Тогда становится понятно, почему нет отдельного события для завершения программы: если программа уже приостановлена, Windows придется возобновить работу приложения только для того, чтобы инициировать событие завершения!)

Класс Application определяет для этой цели два события: Suspending и Resuming. Событие Suspending намного важнее Resuming. Оно используется приложениями для сохранения несохраненных данных в локальном хранилище приложения. Программе не нужно восстанавливать эти данные при получении события Resuming - Windows останавливает приложение самостоятельно. От программы потребуется лишь заузить данные при следующем запуске.

Однако приложение, если сочтет нужным, также может выполнить и другие операции при обработке Suspending и отменить их при обработке Resuming. Например, для сокращения затрат памяти приложение может освободить большие ресурсы, которые позднее можно будет создать заново, или же использовать событие Resuming для обработки обновленных данных, полученных из веб-источника.

Программы, работающие под управлением отладчика Visual Studio, не приостанавливаются и не продолжают свое выполнение так, как это происходит обычно при автономном выполнении. Приложение, работающее само по себе, приостанавливается когда программа перестает выполняться в активном режиме, но с программами под управлением отладчика Visual Studio этого не происходит.

Другое различие: программа не приостанавливается перед аномальным завершением. Аномальное завершение может произойти из-за необработанного исключения или (помните об этом в своих экспериментах) при использовании команды Stop Debugging в Visual Studio.

С другой стороны, при завершении приложения в отладчике Visual Studio клавишами Alt+F4 программа получит событие Suspending и будет завершена, но этот процесс откладывается примерно на 10 секунд, и все это время Visual Studio считает, что программа выполняется!

Для решения этих проблем в Visual Studio на панели инструментов Debug Location имеются команды для ручной приостановки (Suspend), продолжения (Resume) и приостановки с завершением (Suspend And Shutdown) приложений. Эти команды играют исключительно важную роль при разработке кода приостановки и продолжения в программах, выполняемых под управлением отладчика.

Приостановка приложений WinRT в Visual Studio

Из-за всех проблем с анализом обычных событий Suspending и Resuming при выполнении программы в Visual Studio я написал маленькую программу, которая регистрирует эти события в файле, находящемся в локальном хранилище приложения. Предполагается, что программа не будет выполняться в отладчике Visual Studio. Файл XAML программы содержит поле TextBox, доступное только для чтения:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBox Name="txt"
                 AcceptsReturn="True"
                 IsReadOnly="True" />
</Grid>

В файле отделенного кода обрабатываются три события: событие Loaded класса MainPage (срабатывающее один раз при запуске программы), а также события Suspending и Resuming текущего объекта Application. Все эти события регистрируются в файле logfile.txt, сохраняемом в локальном хранилище приложения:

using System;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        StorageFile logfile;

        public MainPage()
        {
            this.InitializeComponent();

            Loaded += OnLoaded;
            Application.Current.Suspending += OnAppSuspending;
            Application.Current.Resuming += OnAppResuming;
        }

        private async void OnLoaded(object sender, RoutedEventArgs args)
        {
            // Создание или получение лог-файла
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;
            logfile = await localFolder.CreateFileAsync("logfile.txt",
                                                CreationCollisionOption.OpenIfExists);

            // Загрузка файла м вывод его содержимого
            txt.Text = await FileIO.ReadTextAsync(logfile);

            // Регистрация запуска
            txt.Text += String.Format("Запуск приложения в {0}\r\n", DateTime.Now.ToString());
            await FileIO.WriteTextAsync(logfile, txt.Text);
        }

        private async void OnAppSuspending(object sender, SuspendingEventArgs args)
        {
            SuspendingDeferral deferral = args.SuspendingOperation.GetDeferral();

            // Регистрация приостановки
            txt.Text += String.Format("Работа приложения приостановлена в {0}\r\n", DateTime.Now.ToString());
            await FileIO.WriteTextAsync(logfile, txt.Text);

            deferral.Complete();
        }

        private async void OnAppResuming(object sender, object args)
        {
            // Регистрация продолжения
            txt.Text += String.Format("Работа приложения восстановлена в {0}\r\n", DateTime.Now.ToString());
            await FileIO.WriteTextAsync(logfile, txt.Text);
        }
    }
}

В обработчике события Loaded программа получает объект StorageFolder, связанный с локальным хранилищем приложения, и создает файл с именем logfile.txt. Благодаря удобному аргументу CreationCollisionOption.OpenIfExists этот вызов CreateFileAsync() эквивалентен GetFileAsync() в том случае, если файл уже существует (как это будет при втором и последующих запусках программы).

С аргументом OpenIfExists файл не открывается для чтения и записи в традиционном смысле (хотя из имени вроде бы следует обратное). Фактическое открытие файла, чтение или запись в него и закрытие выполняются вызовами FileIO.ReadTextAsync() и FileIO.WriteTextAsync().

Обратите внимание на использование объекта SuspendingDeferral в обработчике события Suspending. Без него Windows будет считать, что обработчик Suspending уже завершился при вызове WriteTextAsync(), потому что именно тогда происходит первый выход из обработчика.

Обычно при наличии несохраненных данных в локальном хранилище программа должна загружать данные только по событию Loaded (или другому событию инициализации) и сохранять их по событию Suspending. Наша программа также сохраняет файл по событиям Loaded и Resuming. Хотя программа рассчитана в основном на выполнение вне отладчика Visual Studio, я добавил этот код на случай если программа выполняется под управлением отладчика и завершается командой Stop Debugging. Без этих сохранений данные будут потеряны, потому что обработчик Suspending для таких видов завершения не срабатывает.

Если вы тестируете сохранение и восстановление данных при работе программы в отладчике Visual Studio, лучше привыкните завершать программу командой Suspend And Shutdown (вместо команды Stop Debugging).

Вызов FileIO.ReadTextAsync() можно заменить следующим:

txt.Text = await PathIO.ReadTextAsync("ms-appdata:///local/logfile.txt");

А вызов FileIO.WriteTextAsync может выглядеть так:

await PathIO.WriteTextAsync("ms-appdata:///local/logfile.txt", txt.Text);

Префикс ms-appdata обозначает изолированное хранилище приложения. То, что на первый взгляд кажется каталогом с именем local, в действительности отделяет эту область от roaming или temp. Даже если вы используете эти URI для чтения и записи, все равно необходимо создать объект StorageFile с использованием метода StorageFolder.

Обычно код обновления журнальных файлов присоединяет текст к существующему файлу. В FileIO и PathIO существуют методы присоединения текста, но я решил не использовать их в программе, потому что мне придется либо присоединять один и тот же текст к полю TextBox и файлу журнала, либо перезагружать TextBox из журнала после присоединения.

Следующий пример проекта, как и предыдущий, содержит поле TextBox и сохраняет его содержимое в локальном хранилище приложения. Однако он позволяет ввести текст прямо в TextBox, и конечно, введенный текст автоматически сохраняется до следующего вызова программы. Файл XAML выглядит так:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBox Name="txt"
                 AcceptsReturn="True"
                 TextWrapping="Wrap" />
</Grid>

Файл отделенного кода использует метод FileIO.ReadTextAsync() для чтения файла (потому что у него уже имеется объект StorageFile), но для записи используется метод PathIO.WriteTextAsync():

using System;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            Loaded += OnLoaded;
            Application.Current.Suspending += OnAppSuspending;
        }

        private async void OnLoaded(object sender, RoutedEventArgs args)
        {
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;
            StorageFile storageFile = await localFolder.CreateFileAsync("QuickNotes.txt",
                                                            CreationCollisionOption.OpenIfExists);
            txt.Text = await FileIO.ReadTextAsync(storageFile);
            txt.SelectionStart = txt.Text.Length;
            txt.Focus(FocusState.Programmatic);
        }

        private async void OnAppSuspending(object sender, SuspendingEventArgs args)
        {
            SuspendingDeferral deferral = args.SuspendingOperation.GetDeferral();
            await PathIO.WriteTextAsync("ms-appdata:///local/QuickNotes.txt", txt.Text);
            deferral.Complete();
        }
    }
}

При разработки веб-приложений вам наверняка необходимо будет следить за новинками в IT-индустрии и увас обязательно возникнет куча вопросов по новым стандартам и API-интерфейсам. Предлагаю посетить популярный IT-блог http://webrazum.com/taxonomy/term/132, на котором можно проследить за новыми тенденциями и сразу задать вопрос IT-специалисту.

Пройди тесты
Лучший чат для C# программистов