Pull to refresh

Некоторые особенности реализации фоновых приложений для iOS 4

Reading time7 min
Views13K
В статье речь пойдет об опыте реализации фоновых приложений для платформы iOS 4 в рамках работ над проектом Viber компании Viber Inc. Первая версия Viber была доступна пользователям AppStore 2 декабря 2010 года. Количество пользователей приложения Viber для iPhone в мае 2011 года превысило 15 млн. пользователей. Приложение обладает несколькими интересными особенностями:
  • повторяет интерфейс телефонной книги iPhone;
  • строит социальный граф на базе телефонной книги пользователя;
  • позволяет звонить и отправлять короткие сообщения между пользователями Viber.


Версия под операционную систему Android сейчас в стадии закрытого бета-теста.

Введение


До выхода iOS 4 у разработчиков приложений для iPhone не было возможности выполнять задачи в фоновом режиме и поддерживать постоянное соединение с сервером для оперативного уведомления пользователей о событиях и обновления информации для приложения, не запущенного в данный момент. Единственным ограниченным способом уведомить пользователей о событиях с сервера был механизм push-уведомлений, обладающий следующими недостатками:
  • медленная доставка уведомления, поскольку используются промежуточные сервера Apple с возможной задержкой в несколько минут, что недопустимо при оповещении пользователя о входящих звонках в VoIP приложение;
  • небольшой (256 байт) объем информации, которая может быть передана в уведомлении;
  • при отмене пользователем push-уведомления приложение не получает данных из уведомления.
Возможность выполнять задачи в фоновом режиме может потребоваться для приложений: а) связанных с навигацией и(или) отслеживающих перемещение пользователя (запись GPS-траекторий); б) требующих оперативного уведомления пользователя о событиях на сервере; в) проигрывание музыки (например, интернет радио).

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

В цикле статей планируется рассказать про найденные опытным путем способы решения различных проблем, связанных с выполнением задач в фоновом режиме при разработке приложений для платформы iOS 4. В настоящей статьей будут рассмотрены базовые вопросы выполнения задач в фоновом режиме с использованием одного из трех типов фоновых приложения платформы iOS 4 voip.

В iOS 4 доступны три типа фоновых приложения (они могут быть использованы в приложении одновременно):
  • voip, предназначен для приложений, позволяющих совершать телефонные звонки через интернет;
  • audio, дает возможность записи и проигрывания звука, в том числе по сети и через AirPlay;
  • location, позволяет получать данные о местоположении. Для того, чтобы сообщить операционной системе, что приложение поддерживает фоновое выполнение, необходимо добавить строки с типами, предполагающими фоновое выполнение задач (voip, audio, location) в массив UIBackgroundModes файла Info.plist.
Когда iOS переводит приложение в фон (вызывается метод applicationDidEnterBackground:), можно запросить дополнительное время для завершения текущей задачи, например, для загрузки или скачивания файла путем посылки экземпляру UIAppication сообщения beginBackgroundTaskWithExpirationHandler: с блоком-параметром, который будет вызван незадолго до того, как время фонового выполнения приложения истечет. Когда задача завершена, необходимо послать сообщение endBackgroundTask:. Операционная система дает около 10 минут времени для работы в фоне.

У разработчиков появилась еще одна новая возможность, связанная с фоновым выполнением задач: посылка локальных уведомлений. Само уведомление очень похоже на remote push, однако не требует подключения к серверу и посылается приложением самому себе. При этом приложение, работающее в данный момент в фоне, может послать такое уведомление немедленно, чтобы привлечь внимание пользователя. Для приложения в фоновом режиме направление локального уведомления может быть единственным способом обратить на себя внимание пользователя. Локальное уведомление может быть направление приложением по заранее определенному времени и(или) с определенным интервалом. При направлении локального уведомления, само приложение может быть выключено (например, будильник/таймер). Максимальное количество активных уведомлений приложения не может превышать 128 активных уведомлений в очереди.

Фоновый класс voip


Рассмотрим подробнее некоторые особенности использования фонового класса voip. Прежде всего, это возможность постоянного соединения с сервером для получения актуальной информации о входящих звонках и сообщениях или иных событиях, о которых необходимо сообщать пользователю. Как показывает практика, оперативности remote push часто бывает недостаточно. Для связи с сервером в фоновом режиме может использоваться только один tcp socket, помеченный для iOS специальным образом до возврата из функции applicationDidEnterBackground:. При этом приложение будет приостановлено, но система будет выделять кванты времени для работы в 3-х случаях: а) при появлении входящих данных на сокете; б) при изменении свойств сети, влияющих на доступность сервера, к которому подключен сокет; в) по таймеру в блоке кода, выставленном как обработчик keep-alive, минимальный интервал keep-alive 10 минут. Для привлечения внимания пользователя во время обработки этих событий можно использовать локальные уведомления.

Помимо поддержания связи сервером в фоне, класс voip дает еще одну важную возможность: любое приложение с классом voip в параметрах будет автоматически запускаться в фоне на старте системы и повторно запускаться в фоне после крешей. Важной особенностью является то, что при запуске в фоне метод UIApplicationDelegate applicationDidEnterBackground: вызван не будет, поэтому подключить и пометить сокет как VoIP необходимо до возврата из метода application:didFinishLaunchingWithOptions:, иначе приложение будет просто приостановлено.

Для того, чтобы дать понять iOS, какое из соединений нужно отслеживать в фоне, существует несколько возможностей в зависимости от того, какой из API работы с сетью используется. В случае самого высокоуровневого NSMutableURLRequest достаточно вызвать для него метод setNetworkServiceType: с параметром NSURLNetworkServiceTypeVoIP. Если работа идет на уровне NSInputStream/NSOutputStream, то необходимо выставить значение NSStreamNetworkServiceTypeVoIP для свойства NSStreamNetworkServiceType. В нашем приложении слой работы с сетью написан для поддержки максимального количества платформ, поэтому используются обычные BSD-сокеты. API для конфигурации BSD-сокетов как voip не существует, поэтому необходимо создать пару потоков из уже подключенного сокета с помощью CFStreamCreatePairWithSocket и затем выставить свойство kCFStreamNetworkServiceType потоков в kCFStreamNetworkServiceTypeVoIP.

Конфигурация voip-сокета должна быть обязательно завершена до возврата из методов application:didFinishLaunchingWithOptions: и applicationDidEnterBackground: (а так же до возврата из обработчика keep-alive либо изменения свойств сети в случае, если в нем производилось переподключение к серверу), поэтому для подключения нужно использовать синхронные API, что зачастую неудобно, либо подключаться к серверу и конфигурировать voip-сокет в отдельном потоке, и блокировать главный поток до завершения подключения. Наиболее простой способ синхронизации в этом случае — использование NSConditionLock. Это высокоуровневый интерфейс для posix conditional variables, пользоваться которым гораздо проще. Простой NSLock или posix mutex использовать нельзя, поскольку блокироваться он должен в одном потоке, а разблокироваться в другом. Пример кода подключения в фоне:

// Состояния блокировки<br/>
enum {<br/>
EConnecting,<br/>
EConnected,<br/>
};<br/>
 <br/>
// Объявление переменной, хранящей блокировку<br/>
NSConditionLock * waitForConnectionLock = nil;<br/>
 <br/>
// Код подключения, вызываемый из методов <br/>
// application:didFinishLaunchingWithOptions:, applicationDidEnterBackground:<br/>
// и обработчика keep-alive в главном потоке<br/>
waitForConnectionLock = [[NSConditionLock alloc] initWithCondition:EConnecting];<br/>
 <br/>
// Запускаем подключение к серверу в отдельном потоке<br/>
[server performSelectorInBackground:@selector(connect) withObject:waitForConnectionLock];<br/>
 <br/>
// Выполняем другую необходимую работу<br/>
…<br/>
// Пытаемся захватить блокировку со статусом EConnected до тайм-аута<br/>
// Подробнее о значении тайм-аута далее<br/>
if ([waitForConnectionLock lockWhenCondition:EConnected beforeDate:timeout]) {<br/>
[waitForConnectionLock unlock];<br/>
// Блокировка получена до тайм-аута, значит, подключение успешно, можно пометить         <br/>
// сокет как voip<br/>
[server markSocketAsVoIP];<br/>
}<br/>
// удаляем блокировку<br/>
[waitForConnectionLock release];<br/>
// Можно возвращать управление системе<br/>
return;<br/>
 <br/>
//-----------------<br/>
// Код метода connect<br/>
// Захватываем блокировку, ее статус нас не интересует, он может быть только EConnecting<br/>
[waitForConnectionLock lock];<br/>
// Подключаемся к серверу любым способом<br/>
…<br/>
// Если подключение успешно, освобождаем блокировку со статусом EConnected<br/>
// Код в главном потоке сможет ее захватить<br/>
[waitForConnectionLock unlockWithCondition:EConnected];

Класс фонового выполнения voip будет полезен любым приложениям, которым необходимо своевременно извещать пользователя о событиях с сервера либо накапливать актуальную информацию, не обязательно связанным с VoIP телефонией.

Заключение


В следующей статье более подробно рассмотрим обработчики keep-alive и изменения состояния сети, как правильно вычислить тайм-аут подключения, так же коснемся проигрывания звука в фоне.
Tags:
Hubs:
Total votes 32: ↑26 and ↓6+20
Comments9

Articles

Information

Website
synesis.ru
Registered
Founded
Employees
101–200 employees
Location
Россия