15 марта в 11:51

Как пробудить приложение в iOS: по расписанию и не очень

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

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

В iOS существует несколько способов пробудить приложение и передать ему контроль над происходящим.

Есть UILocalNotification – отложенные уведомления, привязанные к определенному моменту в будущем. Поскольку мы не знаем, когда поступит звонок, работать с UILocalNotification для организации телеконференций невозможно.

Есть извлечение данных в фоновом режиме (Background fetch), описываемое UIBackgroudnModes. Оно также привязывается ко времени и может будить приложение для скачивания и обновления данных в нужный момент времени. Так как мы не можем указать точное время звонка, фоновое пробуждение работать со звонками тоже не может.

Будить приложение можно удаленными уведомлениями. Если вы делаете свой проект, то включается эта возможность в закладке проекта “Capabilities” в Xcode, там есть раздел “Background Modes” и опция “Remote notifications” (также можно включить ключ UIBackgroundModes со значением “remote-notification” в Info.plist).



В самом уведомлении необходимо сделать ключ content-available со значением 1:

{
	"aps" : {
    	"content-available" : 1
	},
	"content-id" : 42
}

только тогда приложение проснется (или запустится) и вызовет метод application:didReceiveRemoteNotification:fetchCompletionHandler. Как раз в нем вы можете уже реализовать нужный функционал.

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
     fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
	NSLog(@"Remote Notification userInfo is %@", userInfo);
 
	NSNumber *contentID = userInfo[@"content-id"];
	// Do something with the content ID
	completionHandler(UIBackgroundFetchResultNewData);
}

В этом обработчике вы можете загрузить нужный контент, подключиться к серверу и, например, проверить наличие вызовов (для работы с пушами рекомендую статью “Уведомления в iOS 10” от e-Legion, в котором рассмотрены вопросы более подробно).

Apple для VoIP приложений рекомендует пробуждение как раз через UIBackgroundModes, но, конечно, не по времени, а по прослушиванию входящего трафика. Мессенджер в фоне мониторит определенный сокет и при появлении в нем трафика вызывает обработчик, запускающий нужные процедуры.

Пуши Voximplant для iOS основаны как раз на VoIP варианте. Для работы нужно:

1. Подписаться на уведомления при старте приложения:

PKPushRegistry *voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
voipRegistry.delegate = self;
voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];

2. Обработать входящие сообщения и передать входящий токен в SDK:

- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
	// 'self.sdk' is an instance of the 'VoxImplantSDK'
	[self.sdk registerPushNotificationsToken:credentials.token];
}

3. После получения пуша, если приложение не подключено к облаку Voximplant, нужно переподключиться и получить входящий звонок:

- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type {
	NSDictionary* custom = [[payload.dictionaryPayload objectForKey:@"aps"] objectForKey:@"voximplant"];
	if (custom) {
    	// This call notifies Voximplant cloud that notification is successfully received.
    	[self.sdk handlePushNotification:payload.dictionaryPayload];
 	}
}

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

Иллюстрация до ката: www.davidrevoy.com
Автор: @mdnsresponder

Комментарии (15)

  • 0
    Разве публикация картинки без указания авторства не нарушает лицензию CC?
    Автор David Revoy.
    • 0
      Справедливо, дописал в конце attribution
  • +1
    Так если у вас VOIP приложение, зачем вам пуши? Слушайте себе сокет и стартуйте сервис по запросу извне.
    • +2
      В iOS это больше не работает
      • 0
        Не совсем. Все работает при компляции с SDK 9.x и ниже — оставлено в целях совместимости, потому что нельзя просто отключить что-то, что раньше всегда работало. Более того, можно компилировать на SDK 10.x, а потом пропатчить заголовок бинарника, где как раз указана версия SDK. В итоге и старое поведение работает, и SDK свежий используется. Методы все эти рабочие и последний даже есть вероятность, что в AppStore сможет попасть.
        • –1
          Как вы думаете зачем в Apple решили сделать не так как раньше? Вряд ли от нечего делать :)
          • 0
            Я прекрасно понимаю, зачем они это сделали, и новое поведение даже выглядит более правильным. Но здесь есть и другая сторона — внезапно они оставляют часть разработчиков за бортом без всякой альтернативы. На форумах эпл уже начитался людей, чьи приложения просто не могут обращаться на push сервера и что-то оттуда качать. И сейчас им говорят очень просто — их кейс более не поддерживается и им нужно решать вопрос самостоятельно. Уговаривать клиентов пропустить трафик на чужой сервер. Там же посоветовали первый способ. Если речь об in-house приложениях, то это отличный вариант. Остается только вопрос, будет ли эпл полностью удалять старое поведение из iOS, ведь это сломает приложения.
            • 0
              iOS 11 прекратит поддержку 32 битных приложений, прогресс не остановить
              • +3
                Это не прогресс, когда функциональность становится хуже. Новое решение не покрывает те задачи, которые решало прошлое.
                • –1
                  ну тогда и электромобили — не прогресс, и вообще FlashPlayer это, оказывается, было добро.
                  • +2
                    Мимо по обоим аналогиями. Электромобили не запрещают продажу всех остальных автомобилей и даже если бы запретили, то полностью покрывают их функциональность. FlashPlayer заменен целым ворохом современных технологий.

                    Что было — открытый приложением сокет в фоне, можно работать полностью в изолированной сети без подключения к серверам эпл и вообще интернета. Стало — подключения в фоне запрещены, приложение пробуждается только через push уведомление, что требует доступа в интернет к серверам эпл.

                    Налицо ухудшение функциональности. Да, старый вариант хуже в плане энергопотребления и именно по этой причине его сменили. Но новый вариант так же и не умеет того, что умел старый, а значит некоторые виды приложений теперь просто невозможно (!!) написать честными прямыми способами. Нужно извращаться с версиями сдк и патчингом бинарников и неизвестно еще, сколько это будет работать, потому что эпл не предоставила решения тех проблем, о которых она не подумала, выпиливая старый функционал, но уже поспешила все отключить.
  • 0

    Работает ли это, если пользователь смахнул приложение из списка открытых или перезагрузил устройство?

    • +1
      В процессе есть свои сложности, но в целом да, работает.
  • 0
    Насколько помню «content-available» перестают обрабатываться ( система перестает будить приложение), если пользователь умышленно прибил его.
  • 0
    Есть извлечение данных в фоновом режиме (Background fetch), описываемое UIBackgroudnModes. Оно также привязывается ко времени и может будить приложение для скачивания и обновления данных в нужный момент времени.

    К сожалению и даже в этом случае все непросто.
    iOS оптимизирует свои процессы, отслеживает как часто пользователь обращается к приложению и подгадывает фоновую загрузку данных к этим моментам.
    Столкнулись с этим во время написания корп.приложения для трекинга мобильных сотрудников в рабочее время.
    Пришлось реализовывать довольно хитрый сценарий, чтобы каждые 10 минут в фоне получать геопозицию и отправлять её на сервер.

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