Тригери і фонові завдання в додатках Windows Store



Ваш додаток, навіть будучи в неактивному стані може реагувати на події, згенеровані системою. Для цього необхідно зареєструвати фонове завдання з допомогою класу 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;
Windows using.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();


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.