Как отключить приостановку приложения UWP?
Я использую C # для разработки приложения UWP для Windows 10, работающего только на настольных компьютерах, для платформы версии 10.0.14393.0. Из-за требований бизнеса жизненный цикл приложения должен вести себя как традиционное приложение Win32.
Поэтому я следовал рекомендации в этой статье запросить ExtendedExecutionSession с ExtendedExecutionReason.Unspecified . Я также настроил Windows, чтобы она никогда не спала и никогда не спала.
Тем не менее, в редких случаях Windows отменяет расширенный сеанс выполнения с причиной SystemPolicy и затем приостанавливает приложение UWP.
- Как я могу получить больше информации (системные журналы? Журналы событий?) О том, что привело к тому, что Windows отменила расширенный сеанс выполнения?
- Как я могу избавиться от этих редких случаев приостановки, чтобы жизненный цикл приложения UWP вел себя точно так же, как приложения Win32 (то есть продолжал работать до тех пор, пока пользователь явно не остановил его запуск)?
5 ответов
ExtendedExecutionSession с ExtendedExecutionReason.Unspecified является предметом многочисленных ограничений в отношении потребления ресурсов. Поскольку у вашего тестового устройства нет батареи, наиболее вероятной причиной приостановки работы вашего приложения является его интенсивное использование памяти. Вы можете попытаться оптимизировать приложение с точки зрения потребления памяти и использовать API управления памятью, как следует из документации, но это не гарантирует, что ваше приложение никогда не будет приостановлено.
Если ваше приложение ориентировано на бизнес-сектор, вы можете рассмотреть возможность использования более мощного ExtendedExecutionForegroundSession вместо ExtendedExecutionSession . Это, вероятно, будет идеальным решением для вашей проблемы. Но это ограниченная возможность, что означает, что приложение, использующее его, не может быть использовано в Магазине Windows — только в Магазине Windows для бизнеса. Вам также необходимо вручную объявить возможность extendedExecutionUnconstrained в манифесте (см. раздел специальных и ограниченных возможностей документации ) для использования API.
В качестве альтернативы вы можете использовать хаки, которые не позволяют приложению приостанавливаться на длительное время:
Использование служб приложений для связи с Приложения для Win32, предложенные pnp0a03 .
Я вспомнил тот предыдущий пост SO — его вопрос был таким: когда я использую сервис приложений для связи с приложениями Win32, он не позволяет приложению переходить в состояние ожидания. Мне удалось воссоздать проблему с примером приложения, которое упоминалось в посте.
Я не знаю, является ли это предполагаемым поведением или нет, но это может помочь разрешить вашу ситуацию.
Согласно этой статье в журнале MSDN вы также можете использовать фоновые задачи для сохранить приложение в живых. В статье утверждается, что фоновые задачи должны быть предпочтительнее метода расширенного сеанса.
Реализация фоновой задачи
В манифесте приложения фоновая задача также должна быть зарегистрирована. Эта регистрация сообщает Windows тип триггера, точку входа и исполняемый узел задачи следующим образом:
Регистрация фоновых задач
Для запуска приложения используйте режим отладки Visual Studio, и приложение никогда не перейдет в приостановленное состояние. Он остается пробужденным Visual Studio.
Ах, это просто шутка.
Я думаю, что, к сожалению, вы ожидаете поведения, которое противоречит принципам разработки UWP.
Система оставляет за собой право приостановить фоновое приложение после некоторого «отступления». Если вы запрашиваете расширенный сеанс выполнения, вам также необходимо подготовиться к отзыву, поскольку подключение к сети не поддерживает работу приложения вечно.
Вариант 1 — прибегнуть к настольному приложению, хотя это может быть и не вариант. Если вы получаете это требование, потому что пользователям нравится UWP, но не нравится идея «приостановлено» (Что . приостановлено ??), я боюсь, что вы не сможете выполнить это требование.
Вариант 2 . есть ли реальная задача, которую нужно поддерживать, даже если приложение переключено в фоновый режим? Если задачу нужно запускать периодически, например, проверять входящие почтовые ящики, вы можете использовать какую-то фоновую задачу, запускаемую по таймеру.
Вариант 3. Подумайте о режиме киоска, на устройстве разрешено запускать только одно приложение, поэтому его нельзя переключить в фоновый режим и приостановить.
Приложение и его жизненный цикл
Работа приложения UWP начинается с класса App, который определен в файлах App.xaml и App.xaml.cs . Класс App является входной точкой в приложение, обеспечивает управление жизненным циклом, а также управляет ресурсами, общими для всего приложения. Кроме того, в классе App можно перехватить исключения, которые могут возникнут в процессе работы. Фактически класс App и представляет приложение.
Приложение может пребывать в пяти различных состояниях:
NotRunning : это состояние возникает, когда пользователь впервые активирует приложение при его установки из магазина, либо когда пользователь закрывает приложение через диспетчер задач, либо завершает его работу, выключая компьютер или осуществляя выход из системы
Running : приложение запущено, и пользователь с ним взаимодействует
Suspended : работа приложения приостановлена, оно не активно
Terminated : работа приложения завершается. Переход в это состояние может быть произведен ОС Windows, если к примеру, приложение уже находится в приостановленном состоянии Suspended, но в связи с нехваткой памяти для других приложений оно переходится в состояние Terminated
ClosedByUser : состояние возникает при закрытии пользователем приложения на крестик или по нажатию клавиш Alt+F4
Переходы из одного состояния в другое можно описать следующей схемой:

Первоначально приложение находится в состоянии NotRunning , оно не работает, и пользователь с ним не взаимодействует. Но вот пользователь нажимает на соответствующий значок или квадратик приложения, и приложение начинает запускаться, класс App, который представляет приложение, начинает работать. Вначале в этом классе вызывается метод OnLaunched() . Если приложение ранее было приостановлено, но пользователь вернулся к нему, то у него также вызывается метод OnLaunched() . Стандартное определение метода OnLaunched:
При вызове метода ему передается параметр типа LaunchActivatedEventArgs , который содержит информацию о предыдущем состоянии приложения, а также аргументы активации. Данный параметр может играть большое значение. Так, когда пользователь вызывает приложение, которое ранее было завершено системой и перешло в состояние Terminated, или которое было завершено пользователем и перешло в состояние ClosedByUser, то свойство PreviousExecutionState этого параметра имеет значения ApplicationExecutionState.Terminated или ApplicationExecutionState.ClosedByUser . А свойство Kind имеет значение ActivationKind.Launch .
Если предыдущее состояние равно ApplicationExecutionState.Terminated , то, возможно, имеет смысл загрузить ранее сохраненные данные. Собственно поэтому по умолчанию в код метода OnLaunched добавлена следующая конструкция:
Если же предыдущее состояние приложения было NotRunning , то приложение фактически стартует с чистого листа. То есть разработчик может быть уверен, что предыдущий запуск приложения не завершился прерыванием работы со стороны операционной системы.
После того, как сработает метод OnLaunched приложение начинает отображаться на экране, и оно переходит в состояние Running .
Сразу после того, как приложение перейдет в состояние Running, срабатывает метод OnActivated() . По умолчанию в классе App этот метод не используется, но мы его можем переопределить. Например:
Здесь опять же мы можем получить из аргумента метода предыдущее состояние. Если оно представляет Terminated, то, к примеру, загрузить ранее сохраненные данные, либо выполнить какую-то другую работу.
Приостановка приложения
Когда пользователь переключается на другое приложение, либо на стартовый экран, происходит приостановка приложения. Если пользователь после этого вернется к ранее приостановленному приложению, то оно снова переходит в состояние Running. При этом переключение состояния происходит не сразу. Если пользователь переключился на другое приложение, то система ждет несколько секунд. И если пользователь в течение этого времени не вернулся в приложение, то оно переходит в состояние Suspended.
Все данные, которые были в приложении до приостановки, сохраняются системой и восстанавливаются при активизации приложения. Однако если данных слишком много, а доступной памяти для них нет, то приложение переводится системой в состояние Terminated и тем самым завершает свою работу. При этом система никак не уведомляет пользователя о том, что приложение было завершено. Поэтому имеет смысл сохранять критические данные в памяти устройства, чтобы после возобновления работы восстановить их в методе OnLaunched или OnActivated.
При переходе приложения в состояние Suspended генерируется событие Suspending . И если мы посмотрим на определение класса App, то увидим в нем обработчик, который вызывается при переходе приложения в состояние Suspended:
Как правило, в обработчике OnSuspending происходит сохранение важных данных приложения, что может спасти их от потери в случае перевода приложения в состояние Terminated. Также здесь можно освобождать некоторые ресурсы, которые могут связаны с камерой, устройствами ввода-вывода и т.д. И, что важно, выполнение кода в этом обработчике должно занимать очень мало времени, оптимально — не более 5 секунд, так как если обработчик будет выполняться несколько секунд, то система сочтет, что приложение перестало отвечать на запросы и должно быть переведено в состояние Terminated.
В начале в обработчике следует получить объект deferral: var deferral = e.SuspendingOperation.GetDeferral(); . Этот объект позволит системе дать приложению время на выполнение асинхроннных операций.
После выполнения всех операций мы помечаем полученную задачу как завершенную с помощью вызова deferral.Complete()
Возобновление работы
Когда пользователь возвращается к ранее приостановленному приложению, оно вновь переходит в состояние Running. При этом генерируется событие Resuming . И если нам надо при этом выполнить некоторую логику, то мы можем обработать это событие:
Завершение работы
После окончания работы с приложением пользователь может сам закрыть приложение с помощью крестика или по нажатию на комбинацию клавиш ALT+F4. После это приложение вначале приостанавливается, а затем завершается, переходя в состояние NotRunning. При этом приложение также генерирует событие Suspending , которые мы можем обработать, например, чтобы сохранить состояние приложения.
Application Lifecycle в приложениях Windows 8.1 и UWP

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

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

Как вы можете догадаться исходя из этой картинки у приложения есть максимум 5 секунд, чтобы завершить свою работу перед переходом в состояние Suspended. Если вы пользуетесь Windows 10, то в диспетчере задач на закладке «Подробности» можете увидеть список процессов, а также приложений, которые на данный момент находятся в состоянии «Приостановлено», т.е. Suspended. В Windows 8.1 информацию о состоянии приложения тоже можно найти в диспетчере задач.
Для того чтобы протестировать код Suspending и Resuming можно отобразить панель с переходами в режимы работы приложения. Для этого необходимо в меню «Вид» — «Панели инструментов» выбрать «Место отладки». И тогда с помощью вот такой панели мы сможем вызвать необходимое событие жизненного цикла.
Давайте разберем, как в коде C# сохранить состояние приложения. Сначала после инициализации приложения добавим обработчик события (метод с верной сигнатурой).
Сам метод сохранения данных выглядит так:
Обратите внимание, что код метода содержит deferral, который чем то немного подобен транзакции. Если ОС потребуется завершить работу приложения, то она будет ожидать выполнения всего кода, заключенного между объявлением deferral и его завершением. Но помните что у вас всего 5 секунд на сохранение данных.
Аналогично событию Suspending есть событие Resuming. С помощью его можно отловить событие восстановления работы приложения из состояния Suspended. Но обратите внимание, что событие Resuming произойдет только если приложение восстановлено. Если оно запущено заново, то это событие выполнено не будет, поэтому в зависимости от потребностей можно добавлять в код обработку событий OnLaunched или OnActivated (со всеми его вариациями – OnFileActivated, OnSearchActivated и т.п.).
Если использовать OnLaunched или OnActivated, то можно проверить какое было прошлое состояние приложения:
Все, что было сейчас написано подходит и для приложений Windows 8.1 и для приложений Windows UWP.
Теперь о том какая фича добавилась в 10-ке. Примерно вот такая схема актуальна для UWP:
Как вы можете заметить, добавилась возможность продлить процесс перехода в состояние Suspended. Как правило ненадолго, но даже пара секунд в таких случаях играет роль. В случае, если приложение находится в состоянии Extended Execution и операционная система хочет освободить ресурсы, завершив работу приложения – срабатывает Revoke – это возможность что-то сделать до того как работа приложения будет прервана. На выполнение Revoke вам дается не больше секунды. Зачем нужен Revoke если он такой короткий? Типичный пример сохранить где-либо флаг и при следующем запуске предупредить пользователя, что не все последние данные были удачно сохранены при приостановке приложения.
Кстати, если про Extended Execution уже было сказано и не раз, то некоторые тонкости почти не упоминались. Я попробовал разобраться и рассказать о них вам.
Extended Execution может быть использовано для достижения двух целей. Первая это продлить процесс сохранения настроек при событии Suspending, а вторая это использование в определенном типе приложений, у которых выполнение при Extended Execution может продолжаться бесконечно. Это например приложения, отслеживающие текущую локацию, воспроизводящие аудио или же VOIP приложения. В таком случае выполнение приложения будет чем-то подобно на background task. Оно будет происходит в фоне и не будет иметь доступ к UI. При Extended Execution необходимо обязательно указать причину по которой выполнение приложения должно быть продолжено. Это может быть ExtendedExecutionReason.LocationTracking или SavingData или Unspecified.
Продлить процесс сохранения настроек при приостановке работы приложения можно во время самого события Suspending.

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

Этот способ немного похож на background task, так как при нем приложение работает долго, если не сказать всегда. Это как раз способ для приложений с отслеживанием геолокации и подобных. Таких приложений одновременно в системе может выполняться ограниченное количество, так что при регистрации Extended Execution возможен отказ.
Довольно хороший пример я нашел здесь: The new background features in Windows 10 Разобрался с ним, опробовал, и сейчас расскажу вам.
Для того чтобы работать с геолокацией нам нужно будет в манифесте приложения на закладке «Возможности» поставить галочку напротив пункта «Расположение». После этого начнем с того, что в MainPage добавим ссылки на пространства имен:
После чего добавим переменную в область видимости класса:
В XAML файл в самый первый тэг Page добавляем Loaded=«Page_Loaded». И далее в коде метода Page_Loaded:
Здесь мы инициализируем ExtendedExecutionSession и запрашиваем разрешение на выполнение продления жизни приложения. В зависимости от того разрешено нам или нет мы записываем в настройки значение 1 или 0. После, во время работы приложения мы сможем таким образом определить разрешено ли выполнение в фоне или нет. Это сделано исключительно для примера. В реальном рабочем приложении логирование реализовать можно каким-либо другим способом.
Далее мы создаем объект типа Geolocator и событию PositionChanged назначаем метод Locator_PositionChanged, которое произойдет если текущее расположение изменится как минимум на 100 метров. В этом методе реализуем отображение toast уведомления:
Не забывайте о том, что в выполнении Extended Execution может быть отказано. Это делается для того чтобы у устройства была возможность найти грань между энергосбережением, высокой производительностью и функционалом работающим в фоновом режиме.
После выключения на телефоне режима экономии заряда и запрета выполнения в фоновом режиме большинству приложений (дабы пул не был занят), смог протестировать приложение в действии.