Application Lifecycle в приложениях Windows 8.1 и UWP

0e590b125eb346538b52fd53ededce0b.jpg


В приложениях Windows Store жизненный цикл довольно похож на картинку. Он довольно простой и содержит всего 3 состояния: NotRunning – Running – Suspended

Для себя я мысленно отождествляю его с «Не копать – Копать – Перекур». Опытные работяги знают, что с перекура к работе можно уже не вернуться. Опытные разработчики сохраняют состояние приложения при событии Suspending и возвращают его впоследствии в исходное состояние при возобновлении работы приложения.

В приложениях Windows UWP (Windows 10) все точно так же, но появились новые фичи.
Давайте сначала разберем общее для 8.1 и UWP. На следующей схеме отображен жизненный цикл приложения. Также на ней показано как называются переходы между состояниями.

6112c99d745143ee8a70d88b284a948a.PNG

Немного с другой стороны жизненный процесс приложения показан на следующем рисунке:

b78501bab6f248799238e8734ce5d1e1.png

Из состояния Suspended приложение может перейти в состояние Running или же в случае, если системе необходимы ресурсы, то работа приложения может быть завершена.

Состояния всего 3, хотя перечисление состояний ApplicationExecutionState содержит в себе больше членов: NotRunning, Running, Suspended, Terminated, ClosedByUser.

Последние 2 члена перечисления помогут нам при активации приложения получить информацию о том как приложение было завершено с помощью аргумента IActivatedEventArgs.PreviousExecutionState события OnActivated или же события OnLaunched. Если приложение работает на десктопе, то в режим Suspended оно переходит после того как пользователь сворачивает приложение. В режиме планшета приложение приостанавливается после переключения на другое приложение (в 8 и 8.1 перед приостановкой проходит несколько секунд, в 10-ке все происходит гораздо быстрее).
Вот такая вот чехарда может происходить за время, начиная с запуска и заканчивая завершением приложения:

a6380d9ef5214a679bc7a06d58644f9b.png

Как вы можете догадаться исходя из этой картинки у приложения есть максимум 5 секунд, чтобы завершить свою работу перед переходом в состояние Suspended. Если вы пользуетесь Windows 10, то в диспетчере задач на закладке «Подробности» можете увидеть список процессов, а также приложений, которые на данный момент находятся в состоянии «Приостановлено», т.е. Suspended. В Windows 8.1 информацию о состоянии приложения тоже можно найти в диспетчере задач.

a210f3275fb94786994e0e29ffbcea0d.PNG

Для того чтобы протестировать код Suspending и Resuming можно отобразить панель с переходами в режимы работы приложения. Для этого необходимо в меню «Вид» — «Панели инструментов» выбрать «Место отладки». И тогда с помощью вот такой панели мы сможем вызвать необходимое событие жизненного цикла.

2a29ce15925b4a7995f03f3e3f0c31d8.PNG

Давайте разберем, как в коде C# сохранить состояние приложения. Сначала после инициализации приложения добавим обработчик события (метод с верной сигнатурой).

     public App()
        {
            this.InitializeComponent();
            this.Suspending += OnSuspending;
        }


Сам метод сохранения данных выглядит так:

  private async void OnSuspending(object sender, SuspendingEventArgs e)
        {
            var deferral = e.SuspendingOperation.GetDeferral();

            //TODO: Сохранить состояние приложения и остановить все фоновые операции. Например, так:
            Windows.Storage.ApplicationData.Current.LocalSettings.Values["NumberOfN"] = n;

            deferral.Complete();
        }


Обратите внимание, что код метода содержит deferral, который чем то немного подобен транзакции. Если ОС потребуется завершить работу приложения, то она будет ожидать выполнения всего кода, заключенного между объявлением deferral и его завершением. Но помните что у вас всего 5 секунд на сохранение данных.

Аналогично событию Suspending есть событие Resuming. С помощью его можно отловить событие восстановления работы приложения из состояния Suspended. Но обратите внимание, что событие Resuming произойдет только если приложение восстановлено. Если оно запущено заново, то это событие выполнено не будет, поэтому в зависимости от потребностей можно добавлять в код обработку событий OnLaunched или OnActivated (со всеми его вариациями – OnFileActivated, OnSearchActivated и т.п.).

Например, так:

        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {

            if (Windows.Storage.ApplicationData.Current.LocalSettings.Values.ContainsKey("NumberOfN"))
            {
                n = (int)Windows.Storage.ApplicationData.Current.LocalSettings.Values["NumberOfN"];
            }

            // ......
        }


Если использовать OnLaunched или OnActivated, то можно проверить какое было прошлое состояние приложения:

protected override void OnLaunched(LaunchActivatedEventArgs e)
        {

            switch (e.PreviousExecutionState)
            {
                case ApplicationExecutionState.Suspended:
                    {
                        // что-то делаем
                        break;
                    }
                case ApplicationExecutionState.Terminated:
                    {
                        // что-то делаем
                        break;
                    }
                case ApplicationExecutionState.ClosedByUser:
                    {
                        // что-то делаем
                        break;
                    }
                case ApplicationExecutionState.NotRunning:
                    {
                    // что-то делаем
                        break;
                    }
            }
                    // … какой-то последующий код 
      }


Все, что было сейчас написано подходит и для приложений Windows 8.1 и для приложений Windows UWP.

Теперь о том какая фича добавилась в 10-ке. Примерно вот такая схема актуальна для UWP:

dee97372b8df4424ade76d14d59f7f8f.PNG

Как вы можете заметить, добавилась возможность продлить процесс перехода в состояние Suspended. Как правило ненадолго, но даже пара секунд в таких случаях играет роль. В случае, если приложение находится в состоянии Extended Execution и операционная система хочет освободить ресурсы, завершив работу приложения – срабатывает Revoke – это возможность что-то сделать до того как работа приложения будет прервана. На выполнение Revoke вам дается не больше секунды. Зачем нужен Revoke если он такой короткий? Типичный пример сохранить где-либо флаг и при следующем запуске предупредить пользователя, что не все последние данные были удачно сохранены при приостановке приложения.
Кстати, если про Extended Execution уже было сказано и не раз, то некоторые тонкости почти не упоминались. Я попробовал разобраться и рассказать о них вам.

Extended Execution может быть использовано для достижения двух целей. Первая это продлить процесс сохранения настроек при событии Suspending, а вторая это использование в определенном типе приложений, у которых выполнение при Extended Execution может продолжаться бесконечно. Это например приложения, отслеживающие текущую локацию, воспроизводящие аудио или же VOIP приложения. В таком случае выполнение приложения будет чем-то подобно на background task. Оно будет происходит в фоне и не будет иметь доступ к UI. При Extended Execution необходимо обязательно указать причину по которой выполнение приложения должно быть продолжено. Это может быть ExtendedExecutionReason.LocationTracking или SavingData или Unspecified.

Продлить процесс сохранения настроек при приостановке работы приложения можно во время самого события Suspending.

ce752d10774545019e89fa947981ed09.png
     private async void OnSuspending(object sender, SuspendingEventArgs e)
        {
            var deferral = e.SuspendingOperation.GetDeferral();
            using (var session = new ExtendedExecutionSession())
            {
                session.Reason = ExtendedExecutionReason.SavingData;
                session.Description = "Сохраняем данные подольше";
                session.Revoked += ExtensionRevoked;

                var result = await session.RequestExtensionAsync();
                if (result == ExtendedExecutionResult.Denied)                
                    await SavingBasicData();  // занимает до 5-ех секунд
                else
                    await SavingData(); // занимает больше времени            }

            deferral.Complete();
        }

    async Task SavingData()
        {
            // сохраняем данные 
            Windows.Storage.ApplicationData.Current.LocalSettings.Values["NumberOfN"] = n;
            // имитируем бурную деятельность
            await Task.Delay(7000);
        }

        async Task SavingBasicData()
        {
            // сохраняем данные 
            Windows.Storage.ApplicationData.Current.LocalSettings.Values["NumberOfN"] = n;
            // имитируем чуть менее бурную деятельность
            await Task.Delay(4000);
        }

         private void ExtensionRevoked(object sender, ExtendedExecutionRevokedEventArgs args)
        {
            // если система пожелала освободить ресурсы и прервала Extended Execution
        }


Этот способ позволяет нам продлить процесс «засыпания» приложения для того чтобы сохранить все требуемые данные.

Второй способ позволяет нам продлить выполнение кода приложения, внеся код заранее специальным запросом (не в OnSuspending). Желательно выполнять запрос как можно раньше, например в событии OnNavigatedTo() или в Loaded().

9815686680f7439482f7b8a0513ec6f1.png

Этот способ немного похож на background task, так как при нем приложение работает долго, если не сказать всегда. Это как раз способ для приложений с отслеживанием геолокации и подобных. Таких приложений одновременно в системе может выполняться ограниченное количество, так что при регистрации Extended Execution возможен отказ.

Довольно хороший пример я нашел здесь: The new background features in Windows 10 Разобрался с ним, опробовал, и сейчас расскажу вам.

Для того чтобы работать с геолокацией нам нужно будет в манифесте приложения на закладке «Возможности» поставить галочку напротив пункта «Расположение». После этого начнем с того, что в MainPage добавим ссылки на пространства имен:

using Windows.Devices.Geolocation; 
using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
using Windows.ApplicationModel.ExtendedExecution;


После чего добавим переменную в область видимости класса:

ExtendedExecutionSession _session;


В XAML файл в самый первый тэг Page добавляем Loaded=«Page_Loaded». И далее в коде метода Page_Loaded:

private async void Page_Loaded(object sender, RoutedEventArgs e)
        {

            if (_session == null)
            {
                _session = new ExtendedExecutionSession { Reason = ExtendedExecutionReason.LocationTracking };
                _session.Description = "Определяем местонахождение";

                if (await _session.RequestExtensionAsync() == ExtendedExecutionResult.Allowed)
                {
                    Windows.Storage.ApplicationData.Current.LocalSettings.Values["LocationAllowed"] = 1;
                }
                else
                {
                    Windows.Storage.ApplicationData.Current.LocalSettings.Values["LocationAllowed"] = 0;
                }
            }

            Geolocator locator = new Geolocator();
            locator.DesiredAccuracyInMeters = 0;
            locator.MovementThreshold = 100; // 100 метров 
            locator.DesiredAccuracy = PositionAccuracy.High;
            locator.PositionChanged += Locator_PositionChanged;
        }


Здесь мы инициализируем ExtendedExecutionSession и запрашиваем разрешение на выполнение продления жизни приложения. В зависимости от того разрешено нам или нет мы записываем в настройки значение 1 или 0. После, во время работы приложения мы сможем таким образом определить разрешено ли выполнение в фоне или нет. Это сделано исключительно для примера. В реальном рабочем приложении логирование реализовать можно каким-либо другим способом.

Далее мы создаем объект типа Geolocator и событию PositionChanged назначаем метод Locator_PositionChanged, которое произойдет если текущее расположение изменится как минимум на 100 метров. В этом методе реализуем отображение toast уведомления:

        private void Locator_PositionChanged(Geolocator sender, PositionChangedEventArgs args)
        {
            string xml = $@"
            <toast activationType='foreground' launch='args'>
                <visual>
                    <binding template='ToastGeneric'>
                        <text>Ваши координаты</text>
                        <text>Широта: {args.Position.Coordinate.Point.Position.Latitude} - Долгота: {args.Position.Coordinate.Point.Position.Longitude}</text>
                    </binding>
                </visual>
            </toast>";

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xml);

            ToastNotification notification = new ToastNotification(doc);
            ToastNotifier notifier = ToastNotificationManager.CreateToastNotifier();
            notifier.Show(notification);
        }


Не забывайте о том, что в выполнении Extended Execution может быть отказано. Это делается для того чтобы у устройства была возможность найти грань между энергосбережением, высокой производительностью и функционалом работающим в фоновом режиме.

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

© Habrahabr.ru