Сервисы

Сервис (обычно в русской литературе применяется термин служба, но мы будем использовать слово сервис из-за созвучности с именем класса) это объект типа Service, который может выполнять длительные операции в фоновом режиме и который не предоставляет пользовательский интерфейс. Другие компоненты приложения могут запустить сервис и он будет работать в фоновом режиме, даже если пользователь переключится в другое приложение. К тому же, компонент может быть связан с сервисом и даже выполнять межпроцессное взаимодействие с ним. Например, сервис может выполнять загрузку по сети, проигрывать музыкальный файл, записывать или читать из файла или взаимодействовать с поставщиками содержимого, и все это в фоновом режиме.

Сервис может находиться в двух состояниях:

Запущен
Сервис “запущен”, если компонент приложения запустил его с помощью метода startService(). После запуска сервис бесконечно работает в фоновом режиме, даже если запустивший его компонент уже уничтожен. Обычно запущенный сервис выполняет одну операцию и не возвращает результат. Например, это может быть скачивание файла по сети. Когда операция заканчивается, сервис должен сам себя остановить.
Связан
Сервис “связан”, если компонент вызвал метод bindService(). Связанный сервис предлагает клиент-серверный интерфейс, позволяющий компонентам взаимодействовать с ним, отправлять запросы, получать результат, и даже выполнять межпроцессное взаимодействие (IPC). Связанный сервис работает до тех пор, пока другой компонент приложения с ним связан. С сервисом может быть связано несколько компонентов, и как только последний из них разрывает связь, сервис уничтожается.

Хотя в этом документе оба типа сервисов обсуждаются отдельно, ваши сервисы могут работать в любом режиме – они могут быть запущены или связаны. Все зависит от того, как вы реализуете пару методов обратного вызова onStartCommand() для разрешения запуска и onBind() для разрешения связывания.

Независимо от того, запущено приложение, связано или и то и другое вместе, любой компонент может использовать сервис (даже компонент другого приложения), также, как любой компонент может использовать явления – запустив его с помощью намерения Intent. Однако, вы можете объявить частный сервис в манифесте и заблокировать доступ к нему из других приложений. Это мы обсудим ниже.

Внимание: сервисы запускаются в основном потоке процесса – сервис не создает его собственный поток и не запускается в отдельном процессе (если не указано иное). Это означает, что если ваш сервис загружает процессор или блокирует операции (например проигрывает mp3), вы должны создавать для него отдельный поток. Таким образом вы предотвратите потенциальную ошибку Приложение не отвечает (ANR) и пользователь все еще сможет взаимодействовать с приложением в его основном потоке.

Основы

Когда использовать сервисы и потоки?

Сервис это простой компонент, который может работать в фоновом режиме даже если пользователь не взаимодействует с приложением. Поэтому создавайте сервис, только если он вам нужен.

Если вам необходимо выполнять работу за пределами основного потока, но только когда пользователь взаимодействует с приложением – используйте еще один поток, но не создавайте сервис. Например, если вам нужно проиграть музыку, но только пока явление запущено, вы можете создать поток в методе onCreate(), запустить его выполнение в методе onStart() и остановить в методе onStop(). Также подумать об использовании AsyncTask или HandlerThread.

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

Для создания сервиса необходимо создать подкласс Service. В нем необходимо переопределить некоторые методы обратного вызова, которые связаны с его жизненным циклом и предоставляют компонентам механизм связывания. Вот наиболее важные методы, которые необходимо реализовать:

onStartCommand()
Система вызывает этот метод, когда другой компонент, вроде явления, запрашивает запуск сервиса с помощью метода startService(). После выполнения метода сервис запускается и бесконечно работает в фоновом режиме. Если вы реализовали данный метод, позаботьтесь об остановке сервиса после выполнения его работы, вызвав метод stopSelf() или stopService() (Если вы собираетесь предоставлять только связывание, вам не нужно реализовывать данный метод).
onBind()
Система вызывает данный метод, когда другой компонент хочет установить связь с сервисом и вызывает метод bindService(). В вашей реализации метода необходимо предоставить интерфейс, который будет использовать клиент для взаимодействия с сервисом. Для этого метод должен вернуть объект типа IBinder. Этот метод всегда должен быть реализован, но в случае, если сервис не разрешает связывание, метод должен возвращать null.
onCreate()
Система вызывает этот метод при создании сервиса для выполнения первоначальных настроек (до вызова onStartCommand() или onBind()). Если сервис уже запущен, данный метод не вызывается.
onDestroy()
Метод вызывается когда сервис долгое время не используется и должен быть уничтожен. Сервис должен реализовывать данный метод для освобождения ресурсов, вроде потоков, зарегистрированных слушателей, приемников и.т.п. Этот метод вызывается самым последним перед уничтожением сервиса.

Если компонент запускает сервис с помощью метода startService(), сервис продолжает работать, пока не остановит сам себя или пока его не остановит другой компонент с помощью метода stopService().

Если компонент создает сервис с помощью метода bindService(), сервис будет запущен, пока компонент с ним связан.

Система Android принудительно останавливает сервис только когда заканчивается память и ее необходимо освободить для явления, которое получило пользовательский фокус. Если сервис связан с явлением, получившим фокус, то маловероятно, что он будет остановлен, а если сервис объявлен для работы на переднем плане (обсуждается ниже), он практически никогда не будет остановлен. Если же сервис выполняется уже длительное время, система понижает его позицию в списке фоновых задач и он будет первым кандидатом на отключение. Поэтому не забывайте должным образом обрабатывать перезапуск вашего сервиса системой. Если система уничтожила сервис, он перезапускается как только появятся свободные ресурсы (хотя это также зависит от значения, которое возвращает метод onStartCommand(), но об этом поговорим позже). Подробная информация об уничтожении сервисов системой содержится в разделе Процессы и потоки.

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

Объявление сервиса в манифесте

Аналогично явлениям и другим компонентам, вы должны объявить все сервисы приложения в файле манифеста. Для этого добавьте дочерний элемент <service> в <application>, например:

Смотрите подробности в документации по элементу <service>.

Существуют и другие атрибуты элемента <service> для описания свойств сервиса, например разрешений на запуск или имени процесса, в котором сервис должен быть запущен. android:name это единственный обязательный атрибут, который указывает имя класса сервиса. После публикации приложения вы не должны менять его имя, поскольку вы рискуете сломать код, использующий явные намерения для запуска или связывания сервиса. (Смотрите документ Вещи, который нельзя менять).

Чтобы гарантировать безопасность приложения, всегда используйте явные намерения для запуска или связи с сервисами и не объявляйте для них фильтры намерений. Если необходимо допустить “неявность” для запуска сервиса, вы можете объявить для него фильтр намерения и исключить его имя из объекта Intent, но затем вы должны выбрать для намерения пакет с помощью метода setPackage(), который устраняет неопределенность запускаемого сервиса.

К тому же, вы можете гарантировать доступность сервиса только для вашего приложения, установив атрибут android:exported в значение false. Это не позволит другим приложениям запускать сервис даже с помощью явного намерения.

Создание запускаемого сервиса

Запускаемый сервис начинается с выполнения другими компонентами метода startService() и последующим вызовом метода onStartCommand().

Жизненный цикл сервиса не зависит от компонента, который его запустил. То есть сервис должен остановить сам себя после выполнения работы с помощью метода stopSelf(). Его также может остановить другой компонент с помощью метода stopService().

Компонент приложения, например явление, может запустить сервис с помощью метода startService(), передав в него объект типа Intent, описывающий сервис и его данные. Сервис получает переданный объект Intent в методе onStartCommand().

Предположим, что явлению нужно сохранить данные в online базе данных. Для этого оно может поместить данные в объект Intent и запустить сервис, передав этот объект в метод startService(). Сервис получит эти данные в методе onStartCommand(), подключится к интернету и выполнит транзакцию. После этого сервис остановит сам себя и уничтожится.

Внимание: сервисы запускаются в том же процессе, что и приложение, в котором он объявлен, по умолчанию в основном потоке. Не забывайте при выполнении сложных операций помещать сервис в отдельный поток, чтобы избежать снижения производительности приложения.

Существует два класса, которые могут быть расширены для создания сервиса:

Service
Это базовый класс для всех сервисов. Когда вы расширяете данный класс, важно создать новый поток, в котором будет производиться вся работа, поскольку сервис использует основной поток приложения по умолчанию, что может сказать на производительности.
IntentService
Это наследник класса Service, который использует рабочий поток для обработки всех начальных запросов, по одному за раз. Это лучший выбор, если нет требуется, чтобы сервис обрабатывал множество запросов одновременно. Все, что нужно, это объявить метод onHandleIntent(), который получает объект Intent при каждом запросе.

Расширение класса IntentService

Поскольку большинство сервисов не обрабатывает множество одновременных запросов, лучше всего использовать для них класс IntentService.

Класс IntentService делает следующее:

  • Создает отдельный рабочий поток для выполнения всех намерений, переданных в onStartCommand() отдельно от основного потока приложения.
  • Создает очередь задач из которой передается одно намерение за раз в метод onHandleIntent(), поэтому не нужно беспокоиться о мультипоточности.
  • Останавливает сервис после выполнения всех запросов, поэтому вам не нужно вызывать stopSelf().
  • Предоставляет стандартную реализацию onBind(), которая возвращает null.
  • Предоставляет стандартную реализацию метода onStartCommand(), который передает намерения в очередь задач, а затем в метод onHandleIntent().

Получается, что все, что вам нужно сделать, это реализовать метод onHandleIntent(), чтобы выполнить работу для клиента. (Хотя также необходимо обеспечить небольшой конструктор для сервиса.)

Вот пример наследования класса IntentService:

Вот и все, этого достаточно.

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

Например, метод onStartCommand() должен возвращать вызов метода базового класса:

Кроме onHandleIntent(), единственным методом, в котором не требуется вызывать метод базового класса является onBind().

Расширение класса Service

Как мы уже сказали в предыдущем разделе, использование класса IntentService позволяет очень легко запустить сервис. Однако если вам требуется, чтобы сервис выполнялся в мультипоточном режиме (вместо использования очереди задач), необходимо использовать класс Service.

Для сравнения, следующий пример показывает использование класса Service для выполнения такой же работы, как в предыдущем разделе. То есть для каждого нового запроса он использует рабочий поток и обрабатывает только одну заявку за раз.

Как видите, требуется несколько больше кода, чем при использовании класса IntentService.

Однако, поскольку вы обрабатываете самостоятельно каждый вызов метода onStartCommand(), вы можете выполнять множество запросов одновременно. В примере мы этого не показали, но вы можете создавать поток для каждого запроса и выполнять в нем работу.

Помните, что метод onStartCommand() должен возвращать целое число. Это число описывает, как система должна продолжить работу сервиса, если она его уничтожает (как говорилось выше, IntentService содержит реализацию по умолчанию). Возвращаемое значение должно быть одной из следующих констант:

START_NOT_STICKY
Если система уничтожает сервис после окончания работы метода onStartCommand(), сервис не продолжает работу, если нет отложенных намерений. Это самый безопасный вариант, который позволяет избежать запуска сервиса, когда это не требуется и когда приложение может просто перезапустить незаконченные работы.
START_STICKY
Если система уничтожает сервис после окончания работы метода onStartCommand(), сервис перезапускается и вызывает метод onStartCommand(), но не передает в него последнее намерение (передает вместо объекта Intent null), если только до этого не было намерения для запуска сервиса (в этом случае оно выполняется). Это подходит для медиа-плееров, которые не выполняют команды, но непрерывно работают, ожидая задачу.
START_REDELIVER_INTENT
Если система уничтожает сервис после окончания работы метода onStartCommand(), сервис перезапускается и вызывает метод onStartCommand(), передав в него последнее намерений, которое было передано сервису. Любые ожидающие намерения ставятся в очередь. Это подходит для сервисов, активно выполняющие работу, которая должна быть немедленно возобновлена, например загрузка файла.

Запуск сервиса

Вы можете запустить сервис из явления или любого другого компонента приложения, передав объект Intent в метод startService(). Система Android вызывает метод onStartCommand() и передает в него данный Intent (никогда не вызывайте метод onStartCommand() напрямую).

Например, явление может запустить сервис, используя явное намерение:

После завершения метода startService(), система вызывает onStartCommand(). Если сервис еще не создан, сначала будет вызван метод onCreate().

Если сервис не предоставляет связывание, намерения, передаваемые в метод startService() это единственный способ взаимодействия между компонентами приложения и сервисом. Однако, если вы хотите, чтобы сервис возвращал результат, то клиент может создать объект PendingIntent для трансляции (метод getBroadcast()) и передать его в объекте Intent сервису. Сервис может использовать трансляцию для возвращения результата.

Несколько запросов на запуск сервиса приводят к нескольким соответствующим вызовам метода onStartCommand(). Однако требуется всего один запрос на остановку сервиса (с помощью метода stopSelf() или stopService()).

Остановка сервиса

Запущенный сервис должен управлять собственным жизненным циклом. Система не останавливает его и не уничтожает объект сервиса, если только не закончилась свободная память. Сервис может остановить сам себя с помощью метода stopSelf() или его может остановить другой компонент с помощью метода stopService(). После вызова одного из этих методов, система уничтожает объект сервиса как можно скорее.

Однако, если ваш сервис обрабатывает несколько запросов одновременно, вы не должны останавливать сервис после обработки запроса на запуск в методе onStartCommand(), поскольку вы можете получить новый запрос на запуск и остановка сервиса в конце первого запроса прекратит второй. Чтобы избежать этой проблемы, вы можете использовать метод stopSelf(int), чтобы гарантировать, что запрос на остановку всегда основан на самом последнем запросе запуска. При вызове метода stopSelf(int), в него передается идентификатор запроса (startId), которому соответствует запрос остановки. Если сервис получит новый запрос прежде, чем вы вызвали stopSelf(int), идентификатор не будет соответствовать и сервис не будет остановлен.

Внимание: очень важно, чтобы приложение останавливало свои сервисы, когда они не требуются, чтобы не тратить ресурсы системы и заряд аккумулятора. Если это необходимо, другие компоненты могут остановить сервис с помощью метода stopService(). Даже если вы разрешите связывание, сервис всегда должен останавливать сам себя, если метод onStartCommand() был когда-либо вызван.

Создание связанного сервиса

Связанный сервис создается с помощью вызова метода bindService(). Создавайте связанные сервисы, если хотите взаимодействовать с ними из явлений или других компонентов приложения, или раскрыть некоторые функциональные возможности других приложений, используя межпроцессное взаимодействие (IPC).

Для создания связанного сервиса, необходимо реализовать метод onBind(), который должен возвращать объект типа IBinder, предоставляющий интерфейс для взаимодействия с сервисом. Другие компоненты приложения затем могут вызывать метод bindService() для получения интерфейса и после этого вызывать методы сервиса. Сервис существует, пока с ним связан хотя бы один компонент приложения, как только последняя связь разорвана, сервис уничтожается.

Для создания связанного сервиса, первое, что вы должны сделать, это объявить интерфейс, который указывает клиентам как с ним взаимодействовать. Этот интерфейс между сервисом и клиентом должен быть реализован в объекте IBinder и должен возвращаться методом onBind().

Несколько клиентов могут установить связь с сервисом. Когда клиент завершает взаимодействие, он вызывает метод unbindService() для разрыва связи.

Есть несколько способов реализации связанных сервисов, и реализация несколько сложнее, чем просто запуск, поэтому ее обсуждение вынесено в отдельный раздел Связанные сервисы.

Отправка уведомления пользователю

После запуска, сервис может отправлять пользователям уведомления используя всплывающие уведомления и уведомления панели состояния.

Всплывающие уведомления, это сообщения, которые появляются на поверхности текущего окна на мгновение, а затем исчезают. Уведомление панели состояния предоставляют значок и сообщение, которые пользователь может выбрать, чтобы что-то сделать (например запустить явление).

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

Запуск сервиса на переднем плане

Сервис на переднем плане это такой сервис, с которым пользователь активно работает и поэтому он не может быть уничтожен при нехватке памяти. Такой сервис должен предоставлять уведомление в панели состояния, которое находится под заголовком “Текущие” и не может быть удалено пока сервис не остановлен.

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

Для запуска сервиса переднего плана, вызовите метод startForeground(). Метод принимает два параметра: целочисленный уникальный идентификатор уведомления и объект Notification для панели состояния. Например:

Внимание: ID для метода startForeground() не должен быть равен 0.

Чтобы удалить сервис с переднего плана, вызовите метод stopForeground(). Метод принимает аргумент типа boolean, показывающий, нужно ли удалить уведомление из панели состояния. Метод не останавливает сервис! Однако, если вы остановите сервис, который все еще находится на переднем плане, уведомление также будет удалено.

Подробную информацию смотрите в разделе Создание уведомлений в панели состояния.

Управление жизненным циклом сервиса

Жизненный цикл сервиса значительно проще, чем у явления. Однако, очень важно, чтобы вы сосредоточили внимание на том, как сервисы создаются и уничтожаются, поскольку сервис может быть запущен в фоновом режиме без ведома пользователя.

Жизненный цикл сервиса – от создания до уничтожения – может пойти в двух направлениях:

  • Запущенный сервис

    Сервис создается другим компонентов с помощью метода startService(). После этого он бесконечно выполняется и должен сам себя остановить, вызвав метод stopSelf() или его может остановить другой компонент, вызвав метод stopService(). Когда сервис останавливается, система его уничтожает.

  • Связанный сервис

    Сервис создается при связывании с ним компонента с помощью метода bindService(). Клиент взаимодействует с сервисом через интерфейс, предоставляемый объектом IBinder. Клиент может закрыть соединение с помощью метода unbindService(). К одному сервису может подключиться несколько клиентов. Система уничтожает сервис, как только отключается последний клиент. Сервису не требуется останавливать самого себя.

Эти пути не являются полностью разделенными. То есть, вы можете выполнить связь с сервисом, который уже был запущен с помощью startService(). Например, фоновый музыкальный сервис может быть запущен с помощью startService() с намерением, в котором передается файл для проигрывания. Позже, когда пользователь захочет управлять воспроизведением или получить информацию о текущей песне, явление может связаться с сервисом, вызвав метода bindService(). В таких случаях вызов методов stopService() или stopSelf() не приведет к остановке сервиса, пока все клиенты не разорвут с ним связь.

Реализация методов обратного вызова жизненного цикла

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

Примечание: в отличие от методов явлений, вы не обязаны вызывать методы базового класса.

Рисунок 2. Жизненный цикл сервиса. Слева показан сервис, созданный с помощью метода startService(), справа с помощью метода bindService().

Реализуя данные методы, вы можете контролировать два вложенных цикла сервиса:

  • Все время жизни сервиса происходит между вызовами onCreate() (в нем происходит первоначальная настройка при создании) и onDestroy() (в нем освобождаются все занятые ресурсы). Например, музыкальный плеер может создавать поток для проигрывания музыки в методе onCreate() и останаливать в поток в методе onDestroy().

    Методы onCreate() и onDestroy() вызываются для всех сервисов, независимо от способа их создания, будь то метод startService() или bindService().

  • Активное время жизни сервиса начинается с метода onStartCommand() или onBind(). Каждый метод принимает объект Intent, переданный из методов startService() или bindService(), соответственно.

    Если сервис запущен, активная фаза жизни заканчивается вместе с сервисом (сервис активен даже после выполнения метода onStartCommand()). Если сервис связан, активная фаза заканчивается после выполнения метода onUnbind().

Примечание: хотя запущенный сервис останавливается с помощью методов stopSelf() или stopService(), не существует соответствующего метода обратного вызова для этой ситуации (нет метода onStop() как у явлений). Так что, если сервис не связан с клиентом, система уничтожает его после остановки, вызывая перед этим метод onDestroy().

Рисунок 2 показывает типичные методы обратного вызова сервиса. Хотя на рисунке сервисы двух типов показаны отдельно, помните, что любой сервис, независимо от того, как он был запущен, может потенциально позволять клиентам устанавливать с ним связь.

Подробная информация о создании сервисов, позволяющих устанавливать с ними связь, содержится в разделе Связанные сервисы.

Добавить комментарий