6 August 2015

Триггеры и фоновые задания в приложениях Windows Store

Development for Windows PhoneC#Development for Windows


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

Например, можно «отловить» событие появления интернета, получения смс, изменения временной зоны или какое-нибудь другое.

Впридачу к этому можно добавить проверку на соответствие состояния устройства/системы определенным условиям. В случае срабатывания триггера будут проверяться все заданные условия.

Кроме системных триггеров существуют различные другие триггеры, которые могут срабатывать по таймеру или в результате особых событий.

Итак, перечисляю возможные виды триггеров, которые используются в приложениях Windows 8.1:
SystemTrigger – Самый популярный триггер. Срабатывает если случается какое-либо системное событие.
Триггеры, которые не требуют регистрации на экране блокировки это:
InternetAvailable — фоновая задача запускается, когда становится доступен Интернет
NetworkStateChange — фоновая задача запускается при изменении состояния сети
OnlineIdConnectedStateChange — фоновая задача запускается при изменении учетной записи Microsoft, подключенной к данной учетной записи
SmsReceived — фоновая задача запускается при получении нового SMS-сообщения
TimeZoneChange — фоновая задача запускается при изменении часового пояса на устройстве
ServicingComplete — фоновая задача запускается, когда система завершает обновление приложения

В случае если приложение зарегистрировано на экране блокировки, становятся доступны еще и следующие триггеры:
UserPresent — фоновая задача запускается, когда пользователь возвращается
UserAway — фоновая задача запускается, когда пользователь бездействует
ControlChannelReset — фоновая задача запускается, когда канал управления сброшен
SessionConnected — фоновая задача запускается при подключении сеанса
BackgroundWorkCostChange — фоновая задача активируется при изменении оценки фоновой работы

Если вы используете эти триггеры без регистрации на экране блокировки, то строка регистрации фонового задания

BackgroundTaskRegistration task = bgTaskBuilder.Register(); 

вызовет исключение “access denied”.

Для того чтобы среагировать на события регистрации приложения на экран блокировки, есть еще 2 триггера:
LockScreenApplicationAdded — фоновая задача запускается, когда плитка добавляется на экран блокировки
LockScreenApplicationRemoved — фоновая задача запускается, когда плитка удаляется с экрана блокировки

В Windows 10 был представлен новый системный триггер:
PowerStateChange — фоновая задача запускается, когда изменяется статус батареи

Системные триггеры закончились, рассмотрим другие доступные триггеры.

MaintenanceTrigger – Самый простой и самый клевый триггер. Работает как таймер. Вы устанавливаете свой интервал, который должен быть не менее 15-ти минут и, в случае если PC подключен к питанию AC, триггер сработает. Что радует, так это то, что этот триггер не требует регистрацию приложения на экране блокировки! Все что требуется, так это то, что PC должен быть включен в розетку.
Если время интервала FreshnessTime установлено в значение меньше 15-ти минут, то при попытке регистрации триггера будет выброшено исключение.

TimeTrigger – практически тоже самое что и MaintenanceTrigger, но не требует питания от сети, зато требует регистрацию приложения на экране блокировки.

PushNotificationTrigger – может быть использован для получения raw уведомлений. Требует регистрацию приложения на экране блокировки.

ControlChannelTrigger – для специализированных сетевых возможностей. Требует регистрацию приложения на экране блокировки.

DeviceUseTrigger – предоставляет доступ к сенсорам и периферийным устройствам в фоне. В отличие от других триггеров, выполняется только если приложение приостановлено. Не поддерживает условий исполнения.

Conditions – Условия исполнения
При срабатывании триггера можно проверить следующие условия:
InternetAvailable / InternetNotAvailable
SessionConnected / SessionDisconnected
UserNotPresent / UserPresent

С выходом Windows 10 триггеров значительно прибавилось. Полный список можно найти среди классов
Windows.ApplicationModel.Background

Вот некоторые новые триггеры, которые стали доступны в приложениях Windows UAP:
ApplicationTrigger – с помощью этого триггера можно запустить выполнение background task с любого места кода (по нажатию кнопки, например)
ToastNotificationActionTrigger – происходит в момент совершения пользователем какого-то действия с toast уведомлениями
ToastNotificationHistoryChangedTrigger – Позволяет отследить изменения истории уведомлений. Может быть использован, например, для того, чтобы «отловить» момент очистки сообщений в центре уведомлений.
LocationTrigger – событие изменения локации, запускающее фоновое задание (используется при функции Geofencing)
DeviceServicingTrigger — событие, которое инициируется при длительной операции обновления (встроенного ПО или параметров) устройства
DeviceWatcherTrigger – происходит когда случаются какие-то изменения со списком подключенных устройств

Далее только список остальных новых триггеров (их стало много, правда?):
AppointmentStoreNotificationTrigger
ActivitySensorTrigger
BluetoothLEAdvertisementPublisherTrigger
BluetoothLEAdvertisementWatcherTrigger
CachedFileUpdaterTrigger
ChatMessageNotificationTrigger
ChatMessageReceivedNotificationTrigger
CommunicationBlockingAppSetAsActiveTrigger
ContactStoreNotificationTrigger
ContentPrefetchTrigger
DeviceConnectionChangeTrigger
DeviceManufacturerNotificationTrigger
EmailStoreNotificationTrigger
GattCharacteristicNotificationTrigger
MediaProcessingTrigger
MobileBroadbandDeviceServiceNotificationTrigger
MobileBroadbandPinLockStateChangeTrigger
MobileBroadbandRadioStateChangeTrigger
MobileBroadbandRegistrationStateChangeTrigger
NetworkOperatorNotificationTrigger
NetworkOperatorHotspotAuthenticationTrigger
PhoneTrigger
RcsEndUserMessageAvailableTrigger
RfcommConnectionTrigger
SmartCardTrigger
SmsMessageReceivedTrigger
StorageLibraryContentChangedTrigger
SocketActivityTrigger


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

Если приложение регистрирует фоновое задание, то необходимо, чтобы эта возможность была задокументирована в манифесте. Это можно сделать просто, воспользовавшись графическим редактором манифеста:





В случае если нужно зарегистрировать несколько фоновых заданий, в графическом редакторе манифеста добавляем столько заданий, сколько нужно:



Или же можно вручную открыть манифест любым редактором XML и внутрь тега Application добавить подобную конструкцию:

        <Extensions> 
    <Extension Category="windows.backgroundTasks" EntryPoint="BGTaskMD.ExampleBackgroundTask">
          <BackgroundTasks>
            <Task Type="timer" />
          </BackgroundTasks>
    </Extension> 
    <Extension Category="windows.backgroundTasks" EntryPoint="BGTaskMD.AppUpdateServicingCompleteTask">
          <BackgroundTasks>
            <Task Type="systemEvent" />
    </BackgroundTasks>
        </Extension> 

Приложение Windows 8.1 может автоматически зарегистрировать само себя на экране блокировки, используя:

   BackgroundAccessStatus accessresult = await BackgroundExecutionManager.RequestAccessAsync();

в результате чего пользователю будет предложено добавить App на Lock Screen.
Этот метод возвращает перечисление BackgroundAccessStatus

Приложения Windows 10 и Windows Phone 8.1 не требуют регистрации на экране блокировки, но вызов RequestAccessAsync перед регистрацией Task-а для них остается обязателен.

Есть 2 варианта регистрации приложения на экране блокировки – badge и badgeAndTileText. Только бэдж и бэдж с текстом. Опцию можно выбирать как через графический редактор манифеста, так и через редактирование кода XML. Впридачу к выбору опции необходимо задать картинку для бэджа — BadgeLogo:

<uap:VisualElements DisplayName="Background Task example" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="BGTaskExample" BackgroundColor="transparent">
        <uap:LockScreen Notification="badgeAndTileText" BadgeLogo="Assets\BadgeLogo.png" />
        <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" ShortName="Background Task example">
        </uap:DefaultTile>
        <uap:SplashScreen Image="Assets\SplashScreen.png" />
      </uap:VisualElements> 

Через графический редактор манифеста, конечно, это сделать удобнее:





Для того чтобы приложение зарегистрировалось на экране блокировки в режиме badgeAndTileText необходимо проверить, что у него имеется еще и картинка для широкой плитки — WideLogo.

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

Приложения, которые зарегистрированы на экране блокировки имеют больше привилегий по отношению к остальным приложениям. Они могут использовать большее количество ресурсов.
Лимиты на использование ресурсов необходимы для того, чтобы устройство пользователя не «ловило тормоза» от постоянно выполняющихся в фоне задач и не сажало аккумулятор.

Например:
Если приложение зарегистрировано на экране блокировки, то оно может использовать 2 секунды времени CPU каждые 15 минут.
Если приложение не зарегистрировано на экране блокировки, то оно может использовать только одну секунду времени CPU каждые 2 часа.
Если это приложение для Windows Phone, то оно может использовать 2 секунды каждые 15 минут.

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

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

После обновления приложения возникает необходимость обновить и фоновое задание (разрегистрировать и зарегистрировать заново). «Отловить» момент обновления приложения нам поможет триггер ServicingComplete, упомянутый ранее. Использовать этот триггер соответствует правилам хорошего тона (практически must use).
Разрегистрировать можно так:

            foreach (var _task in BackgroundTaskRegistration.AllTasks)
            {
                if (_task.Value.Name == "My demo task")
                {
                    _task.Value.Unregister(true);
                }
            }

Если значением метода Unregister задать true, то все выполняемые в настоящее время экземпляры фоновой задачи будут отменены. Если же задать false, то задачам будет дана возможность завершить свою работу.
Кстати, даже после перезагрузки компьютера триггеры все еще работают.

При инициализации триггера вторым параметром задается так называемый параметр OneShot, который указывает, будет ли триггер выполнен только один раз или множество раз. Например:

   SystemTrigger taskTrigger = new SystemTrigger(SystemTriggerType.ServicingComplete,false);


Небольшое руководство к действию
Создаем проект типа Blank App (Universal Windows)
В создавшееся решение добавляем еще один проект типа Windows Runtime Component (Universal Windows)
В первый проект добавим ссылку на второй. Сделать это можно так:
в Solution Explorer/Обозревателе решений на названии проекта вызываем контекстное меню, выбираем Add/Добавить – далее Reference…/Ссылка… и в окне, выбрав в меню Projects/Проекты ставим галку напротив нашего второго проекта


В этом файле код класса фонового задания должен быть примерно таким:

    public sealed class ExampleBackgroundTask : IBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {
       	// какой-то код, выполняемый при вызове фонового задания
        }
    }

Добавим пространство имен:

using Windows.ApplicationModel.Background;

Перед регистрацией нужно обязательно проверить не зарегистрирован ли триггер уже, а то получим множество регистраций одного и того же триггера.

            foreach (var _task in BackgroundTaskRegistration.AllTasks)
            {
                if (_task.Value.Name == "My demo task")
                {
			return;
                }
            }

Если триггер требует регистрацию на начальном экране, то перед регистрацией триггера нужно вставить еще и подобный сниппет:

          // запрос регистрации на экране блокировки
            BackgroundAccessStatus accessresult = await BackgroundExecutionManager.RequestAccessAsync();

            if ((accessresult == BackgroundAccessStatus.Denied)||(accessresult == BackgroundAccessStatus.Unspecified))
            {
                return;
            }

Зарегистрировать триггер можно так:

            TimeTrigger taskTrigger = new TimeTrigger(15, false);
            var bgTaskBuilder = new BackgroundTaskBuilder();
            bgTaskBuilder.Name = "My demo task";
            bgTaskBuilder.TaskEntryPoint = "BGTaskMD.ExampleBackgroundTask";
            bgTaskBuilder.SetTrigger(taskTrigger);
// условие, согласно которому триггер будет выполнен только если интернет доступен
            SystemCondition internetCondition = new SystemCondition(SystemConditionType.InternetAvailable);
            bgTaskBuilder.AddCondition(internetCondition);
            BackgroundTaskRegistration task = bgTaskBuilder.Register();

            task.Completed += task_Completed;

// какой-то код пропущен

        void task_Completed(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
        {
      // какой-то код обработки события
        }

Событие Completed происходит в случае если приложение на данный момент выполняется. Если приложение приостановлено (suspended) и после этого завершено (terminated), то событие не происходит. Если приложение приостановлено, а затем возобновлено, то после возобновления срабатывает Completed.

Кроме Completed необходимо обработать также и отмену фоновой задачи – Canceled. Но она обрабатывается не при регистрации таска, а при его реализации, то есть в классе нашего WinMD файла.

    public sealed class ExampleBackgroundTask : IBackgroundTask
    {
        volatile bool _cancelRequested = false;

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
	// здесь код выполняемый в результате выполнения задания
        }

        private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            // для идентификации задания можно использовать sender.Task.Name  
            _cancelRequested = true;
        }
    }

Если во время фоновой задачи выполняется асинхронный код, то для того, чтобы в случае прерывания приложения код завершился корректно необходимо использовать defferal. В таком случае метод Run приобрел бы примерно такой вид:

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            BackgroundTaskDeferral _deferral = taskInstance.GetDeferral();
           await someAsyncTask();
            _deferral.Complete();
        }

Рекомендуется писать непродолжительные задачи, которые не мешают работе системы.

Во время работы фоновой задачи можно записывать значения состояния процесса в настройки — Windows.Storage.ApplicationData.Current.LocalSettings и считывать их при необходимости

Взаимодействие с пользователем в фоновых задачах не используется. Никакие элементы пользовательского интерфейса, кроме уведомлений, плиток и обновлений индикаторов событий из кода фоновой задачи обновлять не рекомендуется.

Отладка background task-а рискует стать утомительным занятием, если вы будете совершать вызываемое триггером событие, трейсить его или вообще ожидать минимум по 15 минут, для того, чтобы сработал MaintenanceTrigger или TimeTrigger (хотя, некоторые разработчики наверняка мечтают о такой отладке).
Для того, чтобы в VS появилась возможность вызвать триггер код нашего фонового задания необходимо вынести в файл метаданных – WinMD. Мы с вами так и сделали, поэтому при отладке сможем увидеть в событиях жизненного цикла приложения наше фоновое задание.

Для того чтобы это сработало, фоновая задача должна быть уже зарегистрирована и ожидать запуска.
Если же вы зарегистрируете фоновое задание с параметром OneShot, то после того, как оно сработает, отладка задачи через Visual Studio не будет более доступна.
Отладка через Visual Studio недоступна для триггеров типа ControlChannelTrigger, PushNotificationTrigger, а также для фоновых задач, использующих SystemTrigger с типом триггера SmsReceived.

Мой пример реализации TimeTrigger и ServicingComplete вы можете найти на GitHub

Что касается работы с сетью интернет, фоновые задания не предназначены для операций скачивания данных большого объема. Зато они удобны для чего-то вроде обновления новостей или мониторинга событий. Если же вы хотите скачать файлы большого размера, то нужно использовать класс BackgroundDownloader.
Рассмотрим простой пример загрузки данных:

using Windows.Networking.BackgroundTransfer;
using Windows.Storage;
// какой-то код пропущен...
            Uri source = new Uri("https://habrastorage.org/files/f0a/5ea/caf/f0a5eacaf8c44f82a92748124c470f91.jpg");

            StorageFile destinationFile = await KnownFolders.PicturesLibrary.CreateFileAsync(
                                                "imagefromhabr.jpg", CreationCollisionOption.GenerateUniqueName);
            BackgroundDownloader downloader = new BackgroundDownloader();
            DownloadOperation download = downloader.CreateDownload(source, destinationFile);
            await download.StartAsync();

Напоследок, а то и так удалился от темы, добавлю, что для краткосрочных операций, предполагающих скачивание небольших ресурсов можно или даже нужно использовать пространство имен Windows.Web.Http

            var uri = new Uri("http://habrahabr.ru/post/264199/");
            var httpClient = new HttpClient();
            try // Всегда отлавливаем exceptions для сетевых async методов
            {
                var result = await httpClient.GetStringAsync(uri);
            }
            catch { }
            httpClient.Dispose();
Tags:фоновыезаданиятриггерtasktrigger
Hubs: Development for Windows Phone C# Development for Windows
+11
9.3k 74
Comments 4
Popular right now
C++ Developer. Professional
December 28, 202060,000 ₽OTUS
Программирование на языке C (Си)
December 14, 202022,990 ₽Специалист.ру
C++ Junior Developer
March 3, 202123,990 ₽Level UP
SEO-специалист
December 7, 202064,900 ₽Нетология
Top of the last 24 hours