Программирование Android Службы Android Sun, April 23 2017  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Службы Android Печать
Добавил(а) microsin   

Service (служба) является компонентом приложения, который может выполнять продолжительные операции в фоновом режиме, и при этом не предоставляет интерфейса для пользователя приложения (GUI). Другой компонент приложения может запустить службу и она будет продолжать работать в фоне, даже если пользователь переключит свое внимание на другое приложение. Дополнительно компонент может привязаться к службе для того, чтобы взаимодействовать с ней, и может даже выполнить обмен данными через межпроцессное взаимодействие (IPC, interprocess communication). Например, служба может обрабатывать транзакции сети, проигрывать музыку, выполнять файловый ввод/вывод или взаимодействовать с провайдером контента (content provider), оцифровывать звук, и все это выполняется на заднем плане (background). Здесь приведен перевод документации [1], все непонятные и незнакомые термины ищите в Словарике [8].

Служба может по существу принять 2 формы: started (запущена) и bound (привязана).

Started. Служба находится в состоянии "started", когда компонент приложения (такой как activity) запустил её вызовом startService(). Будучи запущенной, служба может работать в фоне бесконечно, даже если компонент, который запустил её, был уничтожен. Обычно запущенная служба выполняет одиночную операцию, и не возвращает результат в вызывавший её код. Например, служба может загрузить или выгрузить файл через сеть. Когда эта операция завершена, служба должна остановить саму себя.

Bound. Служба находится в состоянии "bound", когда компонент приложения привязался к ней вызовом bindService(). Привязанная служба предоставляет интерфейс типа клиент-сервер, который позволяет компоненту приложения взаимодействовать со службой, отправлять запросы, получать результаты, и даже делать то же самое с другими процессами через IPC. Привязанная служба работает, пока другой компонент приложения привязан к ней. Одновременно к службе может быть привязано несколько компонентов, но когда они все отвязаны (unbind), служба уничтожается.

Несмотря на то, что в этой документации эти два типа работы службы рассматриваются по отдельности, Ваша служба может работать обоими способами — она может быть запущена (чтобы работать бесконечно) и также позволяет привязку. Это просто вопрос того, реализуете ли Вы или нет методы обратного вызова (callback): onStartCommand() чтобы разрешить компонентам запустить службу и onBind() чтобы разрешить привязку.

Независимо от того, в каком состоянии Ваше приложение - started, bound, или и то, и другое, любой компонент приложения может использовать службу (даже из отдельного приложения), тем же самым путем, как любой компонент может использовать activity — путем запуска его вместе с Intent. Однако Вы можете декларировать в файле манифеста службу как private, и тем самым заблокировать доступ к ней из других приложений. Это будет обсуждаться дополнительно в секции "Декларирование службы в манифесте".

Предупреждение: служба работает в главном потоке процесса, который её — служба не создает свой собственный поток (thread), и не работает в отдельном процессе (за исключением случая, когда Вы это указали специально). Это означает, что если Ваша служба значительно потребляет ресурсы CPU или блокирует выполнение основного процесса (например, проигрывает MP3 или работает с сетью), то Вы должны создать новый поток, в котором сделаете запуск службы. При использовании отдельного потока Вы снизите риск появления ошибок ANR (Application Not Responding) [2], и главный поток приложения останется выделенным для работы с интерфейсом пользователя.

[Что лучше использовать - службу (service) или поток (thread)?]

Служба является просто компонентом, который запускается в фоне, даже если пользователь не работает с приложением. Это происходит в основном потоке приложения, обрабатывающем интерфейс пользователя (main UI thread). Таким образом, Вы должны создать службу только тогда, когда это именно то, что Вам нужно.

Если же нужно выполнять работу вне основного потока приложения (не в main UI thread), но только тогда, когда пользователь работает с приложением, то вместо службы Вам возможно стоит создать новый поток. Например, если Вы проигрываете некую музыку, но только тогда, когда запущена activity Вашего приложения, Вы должны создать поток (thread) в обработчике активности onCreate(), запустить его в onStart(), и затем остановить его в onStop(). Также рассмотрите возможность использования AsyncTask [3] или HandlerThread, вместо того чтобы использовать традиционный класс Thread. См. также [4] для дополнительной информации по поводу использования потоков.

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

[Базовые понятия]

Чтобы создать службу, Вы должны создать подкласс от класса Service (или от одного из его существующих подклассов). В Вашей реализации нужно перезадать некоторые callback-методы (методы обратного вызова), которые обрабатывают ключевые аспекты жизненного цикла службы и предоставляют (для компонентов приложения) механизм привязки службы, если это нужно. Вот самые важные callback-методы, которые Вы должны переопределить (override):

onStartCommand(). Система вызовет этот метод, когда другой компонент, такой как activity, запросит запуск службы вызовом startService(). Как только этот метод завершится, служба будет запущена, и может бесконечно работать в фоне. Если Вы реализовали этот метод, то в Вашей ответственности остановить службу, когда она завершит работу, путем вызова stopSelf() или stopService() (если Вы решили использовать привязку - binding, то Вам не нужно реализовывать этот метод).

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

onCreate(). Система вызовет этот метод, когда служба создана в первый раз, чтобы выполнить однократные процедуры установки (перед тем, как система вызовет либо onStartCommand() либо onBind()). Если служба уже запущена, то этот метод вызван не будет.

onDestroy(). Система вызовет этот метод, когда служба больше не используется и будет уничтожена. Ваша служба должна реализовать этот метод для выполнения очистки любых используемых ресурсов, таких как потоки, зарегистрированное прослушивание событий (registered listeners), приемники (receivers) и т. п. Это последний вызов, который принимает служба.

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

Если компонент вызвал bindService() для создания службы (и onStartCommand() не вызван), то служба работает, пока хотя бы один компонент остался привязанным к ней. Как только убраны все привязки клиентов к службе, система уничтожит службу.

Система Android принудительно остановит службу только тогда, когда памяти станет мало, и понадобится восстановить системные ресурсы для activity, которая получила фокус - внимание пользователя. Если служба привязана к активности, которая имеет фокус пользователя, то она вряд ли будет уничтожена, и если служба декларирована для работы на переднем плане (foreground, это будет обсуждаться далее), то она почти никогда не будет уничтожена. Иначе, если служба была запущена и должно работала, то система понизит её позицию в списке важности фоновых задач, и для этой службы повысится вероятность уничтожения — если служба запущена, то вы должны разработать её так, чтобы она качественно обрабатывала рестарты от системы. Если система уничтожит Вашу службу, она перезапустится как только снова появятся ресурсы (хотя это также зависит от значения, которое было возвращено из onStartCommand(), что будет обсуждаться далее). Для дополнительной информации по поводу того, когда система может уничтожить службу, см. документ [4].

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

[Декларирование службы в манифесте]

Как и активности (и другие компоненты приложения), Вы должны декларировать все службы в файле манифеста приложения (application manifest file). Чтобы декларировать службу, добавьте элемент < service > в качестве дочернего для элемента < application >. Пример:

< manifest ... >
  ...
  < application ... >
      < service android:name=".ExampleService" />
      ...
  < /application >
< /manifest >

См. документацию на элемент < service > для получения дополнительной информации по поводу декларирования сервиса в манифесте.

В элементе < service > Вы можете декларировать разные атрибуты для определения таких свойств службы как права, требуемые для запуска службы и процесса, в котором служба должна работать. Только атрибут android:name является обязательным — он указывает имя класса службы. Как только Вы опубликовали приложение, Вы не должны изменять это имя, потому что если Вы это сделаете, то есть риск испортить работу кода из-за явных зависимостей с намерениями запустить службу или привязаться к ней (читайте блогпост "Things That Cannot Change" - вещи, которые не стоит изменять).

Чтобы обеспечить безопасность приложения, всегда используйте явное намерение (explicit intent), когда запускаете службу или делаете привязку к ней, и не декларируйте intent-фильтров для службы. Если это критически важно, что Вы допускаете некоторую неоднозначность в запуске службы, Вы можете предоставить фильтры intent для Ваших служб и исключить имя компонента из Intent, но тогда Вы должны установить пакет для intent вызовом setPackage(), который предоставляет достаточное разрешение неоднозначности для целевой службы.

Дополнительно Вы можете обеспечить, что Ваша служба будет доступна только для Вашего приложения путем включения атрибута android:exported и установки его в "false". Это эффективно запретит другим приложениям запускать Вашу службу, даже когда используется явное намерение (explicit intent).

[Создание Started службы]

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

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

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

Предположим, что activity нужно сохранить некоторые данные в онлайн базе данных. Тогда activity может запустить сопутствующую службу, и передать ей данные для сохранения путем передачи intent в startService(). Служба примет intent в своем обработчике onStartCommand(), подключится к Интернет и выполнит транзакцию с базой данных. Когда транзакция завершится, служба остановит саму себя и будет уничтожена.

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

Традиционно есть 2 класса, которые Вы можете расширить, чтобы запустить службу:

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

IntentService. Это подкласс от Service, который использует рабочий поток (worker thread), чтобы обработать все запросы запуска, один за другим. Это лучший выбор, если Вам не нужно, чтобы Ваша служба обрабатывала несколько запросов одновременно. Все, что Вам нужно сделать - реализовать обработчик onHandleIntent(), который примет intent для каждого запроса, который Вы можете выполнить как фоновую работу.

Следующие разделы описывают, как Вы можете реализовать свою службу, используя любой из этих классов.

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

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

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

Все это в целом по факту приводит к тому, что все что Вам нужно сделать - только реализовать onHandleIntent(), чтобы сделать работу, предоставленную клиентом. (Хотя также нужно предоставить маленький конструктор для службы.) Вот пример реализации IntentService:

public class HelloIntentService extends IntentService
{
  /**
 * Конструктор необходим, и должен вызвать super IntentService(String)
 * конструктор с именем рабочего потока (worker thread).
 */
  public HelloIntentService()
  {
      super("HelloIntentService");
  }
/** * IntentService вызовет этот метод из рабочего потока по умолчанию с * intent, с которым запущена служба. Когда этот метод завершится, * IntentService остановит службу, как и ожидалось. */ @Override protected void onHandleIntent(Intent intent) { // Обычно тут мы должны делать некую работу, наподобие загрузки файла. // В нашем примере мы просто заснем на 5 секунд. long endTime = System.currentTimeMillis() + 5*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } } }

Здесь приведен пример всего, что Вам нужно предоставить: конструктор и реализация onHandleIntent().

Если Вы также решите переопределить (override) другие callback-методы, такие как onCreate(), onStartCommand() или onDestroy(), убедитесь, что вызвали реализацию super так, чтобы IntentService могла должным образом обработать жизненный цикл worker thread. Например, onStartCommand() должна возвратить реализацию по умолчанию (которая является тем, как intent передана в onHandleIntent()):

@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    return super.onStartCommand(intent,flags,startId);
}

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

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

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

Как Вы видели в предыдущей секции, использование IntentService делает Вашу реализацию started-службы очень простой. Но однако если Вам нужна служба для выполнения в многопоточном режиме (вместо того, чтобы начать обрабатывать запросы через рабочую очередь work queue одним потоком), то Вы можете расширить класс Service, чтобы обработать каждый intent.

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

public class HelloService extends Service
{
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;
// Обработчик, который принимает сообщения от потока. private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { // По правилам мы должны здесь сделать некую работу, // типа загрузки файла. // В нашем примере мы просто засыпаем на 5 секунд. long endTime = System.currentTimeMillis() + 5*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } // Остановка службы с использованием startId, так чтобы мы // не останавливали службу посреди обработки другого // задания. stopSelf(msg.arg1); } }
@Override public void onCreate() { // Запуск потока, на котором работает служба. Обратите внимание, // что мы создаем отдельный поток, потому что по умолчанию служба // работает в главном потоке процесса, который мы не хотим // блокировать. Мы также даем ему приоритет background, так чтобы // интенсивное задействование CPU не ухудшило работу UI. HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start();
// Получение Looper для HandlerThread, и использование его для // нашего Handler. mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); }
@Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// Для каждого запущенного запроса отправляется сообщение для запуска // выполнения работы, и передачи start ID, чтобы мы знали, какой // запрос связан с остановкой, когда вся работа завершена. Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; mServiceHandler.sendMessage(msg);
// Если мы прибиты, то после возврата отсюда произойдет рестарт. return START_STICKY; }
@Override public IBinder onBind(Intent intent) { // Мы не предоставляем возможность привязки, поэтому возвращаем null. return null; }
@Override public void onDestroy() { Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); } }

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

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

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

START_STICKY - если система уничтожит службу после возврата onStartCommand(), произойдет пересоздание службы и вызов onStartCommand(), но последний intent заново передан не будет. Вместо этого система вызывает onStartCommand() с intent равным null, если нет ожидания intent запустить службу, когда эти intent-ы доставлены. Этот вариант подходит для media-плейеров (или подобных сервисов) которые не выполняют команды, но запускаются в бесконечном режиме и ждут поступления задания.

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

Более подробно по каждому из этих возвращаемых значений см. ссылки на документацию по каждой константе.

[Запуск Service]

Вы можете запустить службу из activity или другого компонента приложения путем передачи Intent (указывающего службу для запуска) в startService(). Система Android вызывает метод службы onStartCommand() и передает ей Intent (Вы никогда не должны вызывать onStartCommand() напрямую). Например, activity может запустить службу примера в предыдущей секции (HelloService), используя explicit intent с startService():

Intent intent = new Intent(this, HelloService.class);
startService(intent);

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

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

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

[Остановка службы]

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

Как только поступил запрос на остановку через stopSelf() или stopService(), система уничтожит службу так быстро, как это только возможно.

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

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

Дополнительную информацию про жизненный цикл службы см. в разделе "Управление жизненным циклом Service".

[Создание службы с привязкой (Bound Service)]

Служба с поддержкой привязки (bound service) позволяет компонентам приложения привязаться к ней вызовом bindService(), чтобы установить длительное соединение (и обычно не позволяет компонентам запустить её вызовом startService()).

Вы должны создать службу с привязкой, когда хотите взаимодействовать со службой из activity и других компонентов приложения, или предоставить некоторую функциональность Вашего приложения для других приложений с использованием IPC.

Чтобы создать bound service, Вы должны реализовать callback-метод onBind() для возврата IBinder, который задает интерфейс для обмена со службой. Другие компоненты приложения могут затем вызвать bindService(), чтобы получить интерфейс и начать вызывать методы на службе. Служба живет только тогда, когда к ней привязан компонент приложения, так что когда нет ни одного привязанного компонента, система уничтожит службу (Вам не нужно останавливать привязанную службу как в том случае, когда служба запущена через onStartCommand()).

Для создания bound service первое, что Вы должны сделать, это определить интерфейс, который указывает, как клиент может взаимодействовать со службой. Этот интерфейс между службой и клиентом должен быть реализацией IBinder, и его должна возвратить Ваша служба из callback-метода onBind(). Как только клиент службы получит IBinder, он может начать взаимодействие со службой через интерфейс.

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

Есть несколько способов реализовать bound service, и эта реализация сложнее, чем у запускаемой службы без привязки (started service), так что bound service рассматривается отдельно в документе [5].

[Отправка оповещений (Notifications) пользователю]

Будучи запущенной, служба может оповещать пользователя о событиях с использованием Toast Notifications или Status Bar Notifications.

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

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

Для получения дополнительной информации см. документацию Toast Notifications или Status Bar Notifications.

[Запуск Service на переднем плане (Foreground)]

Служба на переднем плане (foreground service) считается активно используемой пользователем, так что она в меньшей степени является кандидатом на уничтожение при необходимости освободить память. Foreground-служба должна предоставить оповещение в status bar, сопровождаемое заголовком "Ongoing" (в процессе) - означающим, что уведомление не может быть отклонено, если служба не остановлена и не удалена из состояния Foreground.

Например плейер, который воспроизводит музыку из службы, должен запустить её в foreground, потому что пользователь явно оповещен о работе плейера. Оповещение в status bar может показывать текущую песню и позволять пользователю запустить activity для взаимодействия с плейером.

Для запроса запуска службы на переднем плане вызовите startForeground(). Этот метод получает 2 параметра: целое число, уникально идентифицирующее оповещение и Notification для status bar. Пример:

Notification notification = new Notification(R.drawable.icon,
                                             getText(R.string.ticker_text),
                                             System.currentTimeMillis());
Intent notificationIntent = new Intent(this,
                                       ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 
                                                        0,
                                                        notificationIntent,
                                                        0);
notification.setLatestEventInfo(this,
                                getText(R.string.notification_title),
                                getText(R.string.notification_message),
                                pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

Предупреждение: целое число ID, которое Вы даете для startForeground(), не должно быть равно 0.

Для удаления службы из состояния foreground вызовите stopForeground(). Этот метод получает значение boolean, указывающий нужно или нет удалять также и оповещение в status bar. Этот метод не останавливает службу. Однако, если Вы остановите службу, когда она работает в foreground, то оповещение также будет удалено.

Дополнительную информацию по оповещениям см. в документации [6].

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

Жизненный цикл службы немного проще, чем жизненный цикл activity [7]. Однако все равно важно уделить особое внимание тому, как Ваша служба создается и уничтожается, поскольку служба может работать фоновом режиме без оповещения об этом пользователя.

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

• started service - служба создается, когда другой компонент приложения вызвал startService(). Затем служба работает бесконечно, и должна остановится самостоятельно вызовом stopSelf(). Также службу может остановить другой компонент вызовом stopService(). Когда служба остановлена, система уничтожит её.
• bound service - служба создается, когда другой компонент (так называемый клиент) вызывает bindService(). Затем клиент взаимодействует со службой через интерфейс IBinder. Клиент может закрыть соединение путем вызова unbindService(). К одной службе может быть одновременно привязано несколько клиентов, и когда все они отвязаны от службы, система автоматически уничтожит службу (службе не нужно останавливать саму себя).

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

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

Как и activity [7], служба имеет свои callback-методы жизненного циклa, которые Вы можете реализовать для отслеживания изменений состояния службы, и в которых можете выполнить своевременно нужные действия. Следующий шаблон службы демонстрирует каждый из методов жизненного цикла:

public class ExampleService extends Service
{
    int mStartMode;       // показывает, как вести себя, когда служба уничтожена
    IBinder mBinder;      // интерфейс для привязанных клиентов
    boolean mAllowRebind; // показывает, должен ли использоваться onRebind
@Override public void onCreate() { // Вызывается при создании службы. } @Override public int onStartCommand(Intent intent, int flags, int startId) { // Служба запускается в ответ на вызов startService(). return mStartMode; } @Override public IBinder onBind(Intent intent) { // К службе привязался клиент вызовом bindService(). return mBinder; } @Override public boolean onUnbind(Intent intent) { // Все клиенты отвязались вызовом unbindService(). return mAllowRebind; } @Override public void onRebind(Intent intent) { // Клиент привязывается к службе вызовом bindService() после // вызова onUnbind(). } @Override public void onDestroy() { // Служба больше не используется и будет уничтожена. } }

Примечание: в отличие от методов жизненного цикла activity, Вам не нужно вызывать реализации этих callback-методов в суперклассе (superclass это родительский класс Service, от которого расширен Ваш класс службы).

Android-service-lifecycle

Рис. 2. Жизненный цикл службы.

На диаграмме слева показан жизненный цикл службы, созданной вызовом startService(), и диаграмма справа показывает жизненный цикл службы, созданной вызовом bindService().

При реализации этих методов Вы можете отслеживать два вложенных цикла во время жизни службы:

Полное время жизни между вызовом onCreate() и возвратом onDestroy(). Как и в activity, служба делает начальную настройку в onCreate() и освобождает все использовавшиеся ресурсы в onDestroy(). Например, служба проигрывания музыки могла создать поток для проигрывания звука в onCreate(), и затем остановку потока в onDestroy(). Методы onCreate() и onDestroy() вызываются для всех служб независимо от того, были ли они созданы вызовом startService() или bindService().
Активное время жизни начинается с вызовом либо onStartCommand(), либо onBind(). Каждый метод получает Intent, который передан соответственно либо через startService(), либо через bindService(). Если служба запущена, активное время жизни заканчивается в тот же момент, что и полное время жизни (служба все равно остается активной после завершения onStartCommand()). Если служба привязана, то активное время жизни заканчивается, когда завершается onUnbind().

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

На рис. 2 показаны типичные методы обратного вызова (callback) для службы. Поскольку на рисунке показаны отдельно служба, созданная через startService() от службы, созданной через bindService(), имейте в виду, что независимо от того, как служба была запущена, имеется потенциальная возможность привязки клиентов к службе. Таким образом, если служба была изначально запущена вызовом onStartCommand() (клиентом, вызвавшим startService()), служба все еще может принять вызов onBind() (когда клиент вызвал bindService()).

Для дополнительной информации по поводу создания службы, которая предоставляет привязку см. документ [5], где более подробно рассмотрен callback-метод onRebind() в секции "Managing the Lifecycle of a Bound Service".

[Ссылки]

1. Services site:developer.android.com.
2. Как сделать Android-приложение быстрым и отзывчивым.
3. Работа с потоками через AsyncTask.
4. Процессы и потоки.
5. Bound Services site:developer.android.com.
6. Creating Status Bar Notifications site:developer.android.com.
7. Класс Activity.
8. Словарик Android.

 

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


Защитный код
Обновить

Top of Page