0,0
рейтинг
23 ноября 2015 в 18:53

Разработка → Работа iOS App в фоновом режиме

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

Отправлять координаты по таймеру когда программа свернута в фон не составляет проблемы, для этого можно использовать background location mode для получения координат и long-running tasks для таймеров.

Но так как в iOS нет такой прелести как Android Background Services, то если вручную завершить программу, код перестает выполняться. Потому основная сложность заключается в том, как максимально быстро запустить программу в фоне, чтобы она продолжила выполнять свою задачу дальше, если её по каким то причинам выгрузила из памяти iOS, или если пользователь перезагрузил устройство, или если он вручную «убил» программу.

Теперь о том, что помогло решить данную задачу в приемлемом варианте:

— VOIP background mode
Когда включен этот режим фоновой работы, iOS перезапускает приложение после перезагрузки устройства или если программа была по каким то причинам выгружена из памяти самой iOS.
Так же, если программа свернута в фон, этот режим говорит iOS чтобы она не закрывала сокет соединения отмеченные флажком NSStreamNetworkServiceTypeVoIP.
Пример:

[NSStreamInstance setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType];

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

— Significant-change location
Перезапускает программу в фоне по событию изменения координат если она была выгружена из памяти. Метод не совсем надежный, так как трудно предсказать в каком случае iOS запустит программу в фоне, это может произойти как сразу после завершения работы программы, так и через существенный промежуток времени (особенно если устройство не трогать).
Включается этот сервис с помощью вызова метода:

[CLLocationManagerInstance startMonitoringSignificantLocationChanges];

— Region monitoring location
Позволяет указать регион за входом или выходом из которого iOS будет следить и будить программу в случае этих событий.
Потому, как дополнительная мера, когда пользователь выгружает программу из памяти, по событию applicationWillTerminate мы устанавливаем регион для слежения с центром в текущих координатах и радиусом 5 метров (какое-то минимальное значение). И когда пользователь отходит, как показывает практика, где-то на 100 метров от центра заданного региона, iOS перезапускает программу по событию didExitRegion.

Данный метод работает намного точнее и надежнее чем significant-change location.
Пример:

@implementation AppDelegate
- (void)applicationWillTerminate:(UIApplication *)application {
        CLCircularRegion* region = [[CLCircularRegion alloc] initWithCenter:userLocaton.coordinate radius:5 identifier:@"wakeupinbg"];
        region.notifyOnEntry = YES;
        region.notifyOnExit = YES;
        [CLLocationManagerInstance startMonitoringForRegion:region];
    }
@end

— Local Notifications
Используется чтобы оповестить пользователя если программа вдруг выгружается из памяти и не перезапускается.
Работает так:
1) Устанавливается Local Notification которое прозвенит через 10 минут
2) Устанавливается таймер который каждые 7 минуты тикает и проверяет оставшееся время до того как прозвенит Local Notification
3) Если оставшееся время до Local Notification меньше чем 4 минуты, программа удаляет текущий Local Notification и устанавливает новый (пункт 1)
4) Если вдруг программу по какой-то причине выгрузили, то соответственно таймер не выполнится, Local Notification не переустановится (пункт 3 не выполниться), Local Notification прозвенит в обозначенное время и если программу не открыть, он будет звенеть каждую минуту и оповещать пользователя что программа выгружена из памяти и ее надо запустить.

Некоторые важные моменты использования данных подходов:

— Не забыть включить Background modes в настройках проекта, вкладка «Capabilities:



— Необходимо добавить поле NSLocationAlwaysUsageDescription в info.plist:



— После создания CLLocationManager, необходимо запросить разрешение на получение координат у пользователя:

[CLLocationManagerInstance requestAlwaysAuthorization];

— В iOS9 необходимо включить получение координат в фоне для объекта CLLocationManager:

if ([CLLocationManagerInstance respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
   [CLLocationManagerInstance setAllowsBackgroundLocationUpdates:YES];
}

Тестовый проект который можно запустить и посмотреть подробнее как работает данное решение — лежит вот тут: github.com/vaskravchuk/BGModesTest
Так же приложение с подобным решением (только без voip) есть на маркете: Track-kit.

P.S. Заказчик использует Apple Developer Enterprise Program, то есть программа не будет проходить проверку у Apple, потому возможные сложности с этим не учитывались.

P.S.S. Если у вас есть информация как лучше решить проблему с удержанием работы программы в фоне, буду очень рад.
Василий Кравчук @VasKravchuk
карма
32,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • +1
    Enterprise program? Я лишь слышал, что там нет проверок эпл, и если это так, то решением будет fork() и совершение слежки в дочернем процессе.
    1. Регистрируем обработчики сигналов, внимательно смотрим, как система вас убивает и будет ли она трогать вас вообще. Я полагаю, что она будет убивать только процесс самого приложения, а сына оставит в покое.
    2. Если все замечательно и вас даже не убивают, то можно подобрать какуюнибудь точку рандеву, с помощью shmem, pipe или чего бы то ни было еще, что бы проверить, а нужно ли делать форк или сынок уже в работе.

    Это комментарий-предположение. Я полагаю, что никаких препятствий для такой реализации нет, но в этом не уверен.
    • +1
      Добравшись до гугла обнаружилось, что iOS блокирует вызов fork(). http://stackoverflow.com/questions/12088155/fork-failed-1-operation-not-permitted
      А жаль. Интересно, чего бы еще такого придумать в обход Вашего способа…
      • 0
        Вероятно обходом будет самостоятельная имплементация fork если низкоуровневые апи не режутся MAC(Mandatory Access Control). Как вариант думаю еще можно заюзать posix_spawn(https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/posix_spawn.2.html). оно вроде не лочится никак. решение тоже очень простое:
        pid_t pid;
        char *argv[] = {
            "/private/var/mobile/Containers/Data/Application/392A2665-9019-33AC-2E3748331/testapp",
            "param1",
            "param2",
            NULL
        };
        
        posix_spawn(&pid, argv[0], NULL, NULL, argv, environ);
        waitpid(pid, NULL, 0);
        


        ps.: я бы мог еще кое что интересное описать, но как вижу малвар кодеров(даже тех кто пишет концепты) тут не жалуют, поэтому воздержусь.
        • 0
          posix_spawn режется. Имхо режутся все системные вызовы по номерам. Еще варианты?

          Да ладно, не жалуют, самые одаренные малвар-кодеры потом и пишут весь фундамент, на котором все работает. Ну, если в плохих мальчиков надоест играть. :3
  • +1
    раньше вроде был способ с постоянным проигрыванием «пустого» звука, но не знаю как оно ведет себя с другими плеерами вроде стандартного приложения Music
    • 0
      Сейчас такой хак эппл при ревью приложения не пропускает.
      Равно как и использование для целей геолокаций опции VoiceOverIP.
      А вот Region Monitoring — то, что надо.
      • 0
        Пропускает или нет не важно
        а) Enterprise Program не имеет стадии ревью
        б) Все, что не пропускает ревью, можно зарелизить с помощью Dark Release.
        • 0
          Просто придирка:
          б) Как вы зарелизите через Dark Release ключи в Info.plist?
          • 0
            Ключи бы я изначально выставил. А затем предложил бы для товарищей код-ревью бекграунд аудио какое-то, мол, смотрите, вот по делу. А на самом деле с дарк релизом бы эту обманку выключил.
            Для того, чтобы так сильно заморачиваться, должны быть веские причины. Мой посыл лишь в том, что это возможно.
  • +2
    Поясните ещё почему
    Поставленную задачу надо решить только средствами iOS без изменения серверной части (никаких Push Notifications).

    Разве это не решило бы абсолютно все проблемы? Сервер когда хочет, тогда и опрашивает, можно менять эти интервалы. Приложение будет запущено если надо, всё отработает и сдохнет, если система так пожелает.
  • 0
    мде… сколько будут существовать устройства, столько будет существовать шпионский софт.
    кто заказчик-то? милиция? бандиты? ревнивый супруг? папаша-параноик?
    — ни за что бы не взялся за такую работу — противно.
    • 0
      Представьте, что устройства принадлежат компании, она их выдает сотрудникам для использования в служебных целях в служебное время, и потому имеет полное право знать где они находятся.
      С другой стороны, по простому запросу из АНБ в apple/google, ваш собственный телефон будет сообщать где он находится без дополнительного софта. Более того, если вы не изменили настройки по умолчанию, он и так сообщает об этом, нужно только запросить историю ваших перемещений.
      Кстати, для папаш-параноиков существует возможность официально узнавать геопозицию своих чад встроенными в iOS средствами, если они привязаны к одному логину iCloud.
      • +1
        Для контролем за детьми просто надо включить у них Find My Friends и дать себе доступ, даже одна учетка не обязательна.
        • 0
          Работает она, надо заметить, хуже, чем Find My Phone: дольше время до отображения позиции и более редкие обновления.
  • 0
    Я тоже сейчас пытаюсь портировать свое приложение KidsTrack с андроида на iOS, и решаю эту же задачу. Что интересно: на эмуляторе или на устройстве, подключенном через USB все работает прекрасно. При запуске на неподключенном ни к чему устройстве выясняется, что несмотря на то, что вызовы «didUpdateLocations» идут каждую секунду, через 10-15 минут работы в фоне что-то изменяется в системе, и код метода didUpdateLocations не может запустить NSURLConnection через dataTaskWithRequest. Ошибок никаких нет, просто перестает уходить трафик на сервер. Как только задача переходит в foreground, все NSURLConnection просыпаются, и все, что там было, отправляется. Не понятно пока, как это побороть. Я, правда, не использую ни Significant-change location, ни Region monitoring, ни Local Botifications
    • +1
      > Я, правда, не использую ни Significant-change location, ни Region monitoring, ни Local Botifications
      Вот в этом и проблема :)
      Через 10-30 секунд ухода в фон приложение ставится на паузу. И если не отмечены необходимые Background Modes, то и сетевой стек тоже засыпает.
    • 0
      NSURLSession попробуйте
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Программа делалась для курьеров, почтальонов и медсестер, которые работают «в поле» и центральный офис в зависимости от текущего местоположения оптимизирует распределение заказов:)
      • НЛО прилетело и опубликовало эту надпись здесь
  • +1
    я бы ещё добавил, что когда мы добавляем permission на location update (в background mode), при публикации приложения в Apple Store нужно обязательно не забыть в конце дописать:
    “Continued use of GPS running in the background can dramatically decrease battery life.”

    ибо словить из-за этой ерунды rejection очень не приятно (особенно когда стартап)

    2.16

    We found that your app uses a background mode but does not include the following battery use disclaimer in your Application Description:

    “Continued use of GPS running in the background can dramatically decrease battery life.”

    It would be appropriate to revise your Application Description to include this disclaimer.
  • 0
    Что-то я не понял, а как вы таймер проверяете через 7 минут? На выполнение кода в бэкграунд режиме дается же 30 секунд.
    • 0
      Таймер работает пока программа в фоне активна, а для того чтобы она в фоне была активна используется background location mode для получения координат и long-running tasks. Как только программа в фоне умирает, таймер перестает тикать и пользователь получает сообщение что надо запустить программу.
      • 0
        Т.е. если запустить таймер и свернуть приложение (перевести в бэкграунд), то таймер все равно будет работать?
        • 0
          Если свернуть программу, таймер не тикнет. Но чтобы программа была активна какое-то (неопределенное, но около 10 минут) время, после того как пользователь ее свернул, можно использовать механизм Finite-Length Tasks
      • 0
        background location mode вы имеет ввиду, что периодически у вас все же вызывается — (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {… }?
        • 0
          Ага:)
          Задача стояла чтобы программа через вебсокет, каждые N секунд отправляла свои текущие координаты. Потому она в фоне остается активна и у нее переодически вызывается locationManager:didUpdateLocations:
  • 0
    Два дня разбираюсь с работой геолокации в фоновом режиме.
    На IOS7 геолокация работает все время пока программа загружена, даже если она свернута.

    На IOS8 и IOS9 геолокация работает около 18 минут после сворачивания, потом режим background переходит в режим suspend и все глохнет.
    Я что-то делаю неправильно, или получить данные от свернутой программы невозможно через XX минут?

    Также вызывают вопросы некоторые другие особенности работы геолокации. Без установки фильтра на расстояние, запросы идут каждую секунду. После установки фильтра 2 запроса и далее молчание ( если я не двигаюсь). Как-то оба варианта не очень.

    Пробовала allowDeferredLocationUpdatesUntilTraveled, выдает ошибку с кодом 12.

    Буду очень благодарна, если кто-то поделится опытом по этому поводу.

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