Обзор программирования служб Android Печать
Добавил(а) microsin   

Service (сервис, служба) это компонент приложения, который может выполнять долговременные фоновые действия. Service не предоставляет интерфейс пользователя. Будучи запущенной, service может продолжать работать некоторое время, даже после того, как пользователь переключится на другое приложение. Кроме того, компонент может быть привязан к service для взаимодействия с ней, и даже выполнять операции межпроцессного взаимодействия (interprocess communication, IPC). Например, service может обрабатывать сетевые транзакции, проигрывать музыку, выполнять файловый ввод/вывод (file I/O), или взаимодействовать в провайдером контента, и все это делается в фоне, невидимо для пользователя (in background).

Примечание: это перевод документации [1]. Далее по тексту термин service для разнообразия иногда будет заменяться словами сервис или служба. Все эти три термина эквивалентны, и обозначают одно и то же.

Предупреждение: service работает в главном потоке (main thread) своего hosting-процесса. Service не создает своего потока, и не запускает отдельный процесс, если это специально не сделал программист. Вы должны запускать любые блокирующие операции из кода службы в отдельном потоке, чтобы избежать ошибок типа "Приложение не отвечает" (Application Not Responding, ошибка ANR).

[Типы сервисов Android]

Существуют 3 разных типа служб:

Foreground. Это служба, которая выполняет некоторые операции, заметные для пользователя. Например, audio-приложение может использовать foreground-службу для проигрывания звукового трека. Foreground-службы должны отображать оповещения (Notification [2]). Foreground-службы продолжают работать, даже когда пользователь не взаимодействует с приложением.

Когда вы используете foreground service, то должны отображать оповещение, чтобы пользователи знали, что на устройстве запущена служба. Это оповещение не может быть отклонено до тех пор, пока служба не будет остановлена или удалена из системы.

Подробнее про конфигурирование foreground-служб в вашем приложении см. [3].

Замечание: WorkManager API предоставлет гибкий метод планирования запуска задач (scheduling tasks), и это дает возможность при необходимости запустить эти действия как foreground-службу. Таким образом, WorkManager API позволяет достичь того же функционала, что и foreground-служб. Во многих случаях WorkManager более предпочтительный способ, чем непосредственное использование foreground services.

Background. Это служба, которая работает в фоновом режиме, и не сообщает напрямую об этом пользователю. Например, если приложение использует службу для сжатия своего хранилища данных, то обычно это может быть реализовано как background service.

Замечание: если ваше приложение нацелено на API level 26 или более высокий, то система накладывает ограничения на работу background-службы, когда само приложение не находится в фоновом режиме. В большинстве ситуаций вы не должны получить доступ к информации о местоположении из background-службы. Вместо этого запланируйте запуск задач с помощью WorkManager.

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

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

Независимо от того, запущена ли ваша служба, привязан ли к компоненту, или и то, и другое, любой компонент приложения может использовать эту службу (даже из отдельного приложения) таким же способом, как и любой компонент может использовать activity — путем запуска вместе с Intent [4]. Однако вы можете декларировать в файле манифеста службу как частную (private) и тем самым заблокировать к ней доступ из других приложений. Боле подробно об этом см. далее раздел "Декларация службы в манифесте".

[Выбор между службой и потоком]

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

Если вы должы выполнять какую-либо работу вне вашего главного потока приложения (main thread), однако только когда пользователь взаимодействует с приложением, то вы вместо службы должны создать новый поток (new thread) в контексте другого компонента приложения. Наример, если вы хотите проиграть какую-то музыку, однако только когда работает ваша activity, то должны создать поток вызовом onCreate(), запустить его вызовом onStart(), и остановить в onStop(). Также рассмотрите использование пулов потоков (thread pools) и executor-ов из пакета java.util.concurrent или сопрограммы Kotlin [6] вместо традиционного класса Thread [5]. См. документ "Threading on Android" для дополнительной информации, как перенести выполнение действий в фоновые потоки.

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

[Основы работы со службами в Android]

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

onStartCommand()

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

onBind()

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

onCreate()

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

onDestroy()

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

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

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

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

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

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

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

Для информации по деларированию службы в манифесте см. документацию по элементу < service>.

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

Предупреждение: для гарантии безопасности вашего приложения всегда используйте явное намерение (explicit intent), когда запускаете Service, и не декларируйте фильтры намерения (intent filters) для своих служб. Использование неявного намерения (implicit intent) для запуска службы создает дыру в безопасности, потому что вы не сможете быть уверены, что служба отвечает на intent, и пользователь не увидит, какая служба запускается. Начиная с Android 5.0 (API level 21) система выбросит исключение, если вы вызовите bindService() с implicit intent.

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

Замечание: пользователи могут видеть, какие службы работают на их устройстве. Если они увидят службу, которую они не могут распознать или которой не доверяют, то они могут остановить эту службу. Чтобы исключить, что ваша служба будет случайно остановлена пользователями, вам нужно добавить атрибут android:description к элементу < service> в манифесте вашего приложения. В description предоставьте короткое предложение, описывающее, что делает служба, и какие плюшки она предоставляет.

[Создание запускающейся службы]

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

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

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

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

Предупреждение: служба работате в том же процессе, что и приложение, в котором она была декларирована, и по умолчанию в main thread этого приложения. Если ваша служба выполняет интенсивные или блокирующие операции, в то время как пользователь взаимодействует с активностью из того же приложения, то служба замедлит производительность активности. Чтобы избежать визуального замедления работы интерфейса приложения, запустите внутри службы новый поток.

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

Фреймворк Android также предоставляет субкласс IntentService, прозводный от Service, который использует рабочий поток (worker thread) для обработки всех стартовых запросов по одному. Использование этого класса не рекомендуется для новых приложений, так как он не будет хорошо работать начиная с Android 8 Oreo, из-за введенных ограничений на Background выполнение кода. Кроме того, IntentService устарел начиная с Android 11. Вы можете использовать JobIntentService как замену для IntentService, что совместимо с более новыми версиями Android.

В последующих секциях будет показано, как вы сможете реализовать свою собственную службу, однако для большинства вариантов испльзования следует вместо службы рассмотреть вариант применения WorkManager. Ознакомьтесь с руководством по фоновым обработкам в Android [9], чтобы подобрать самое подходящее для себя решение.

Расширение класса Service. Вы можете реализовать производный от Service субкласс для обработки каждого поступающего намерения (intent). Базовая реализация может выглядеть следующим образом:

// Код Kotlin:
class HelloService : Service() {
 
   private var serviceLooper: Looper? = null
   private var serviceHandler: ServiceHandler? = null
 
   // Обработчик, который принимает сообщения от потока:
   private inner class ServiceHandler(looper: Looper) : Handler(looper) {
 
      override fun handleMessage(msg: Message) {
         // Обычно мы должны здесь выполнять какую-либо работу,
         // наподобие загрузки файла. Для нашего примера мы просто
         // заснем на 5 секунд.
         try {
            Thread.sleep(5000)
         } catch (e: InterruptedException) {
            // Восстановление статуса прерываний.
            Thread.currentThread().interrupt()
         }
 
         // Остановка службы с использованием её startId, чтобы мы
         // не остановили службу во время её другой работы:
         stopSelf(msg.arg1)
      }
   }
 
   /////////////////////////////////////////////////////////////////////
   // Переназначение основных callback-методов службы.
   override fun onCreate() {
      // Запуск потока из тела службы. Обратите внимание, что мы создаем
      // отдельный поток, потому что служба обычно работает в контексте
      // процесса main thread, который мы не хотим блокировать. Мы также
      // даем потоку фоновый приоритет (background priority), чтобы
      // он не слишком грузил CPU, и не нарушил тем самым отзывчивость
      // интерфейса пользователя для приложения (UI).
      HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
         start()
 
         // Получение HandlerThread Looper, и использование его в нашем обработчике:
         serviceLooper = looper
         serviceHandler = ServiceHandler(looper)
      }
   }
 
   override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
 
      // Для каждого запроса на запуск отправьте сообщение, чтобы запустить
      // задание и передать идентификатор запуска (startID), чтобы мы
      // знали, какой запрос останавливаем, когда задание завершено:
      serviceHandler?.obtainMessage()?.also { msg ->
         msg.arg1 = startId
         serviceHandler?.sendMessage(msg)
      }
 
      // Если нас прибьют, то после возвращения отсюда делаем restart.
      return START_STICKY
   }
 
   override fun onBind(intent: Intent): IBinder? {
      // Мы не предоставляем привязку к службе, поэтому вернем null:
      return null
   }
 
   override fun onDestroy() {
      Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
   }
} 

public class HelloService extends Service {
   private Looper serviceLooper;
   private ServiceHandler serviceHandler;
 
   // Обработчик, который принимает сообщения от потока
   private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
         // Обычно мы в этом месте выполняли бы полезную работу, наподобие
         // загрузки файла. Для нашего примера просто заснем на 5 секунд.
         try {
            Thread.sleep(5000);
         } catch (InterruptedException e) {
            // Restore interrupt status.
            Thread.currentThread().interrupt();
         }
         // Остановка службы с помощью startId, чтобы не остановить другой
         // экземпляр этой службы, который выполняет другое задание.
         stopSelf(msg.arg1);
      }
   }
 
   /////////////////////////////////////////////////////////////////////
   // Переназначение основных callback-методов службы.
   @Override
   public void onCreate() {
      // Запуск потока запущенной службой. Обратите внимание, что мы создаем
      // отдельный поток, потому что служба обычно работает в контексте
      // процесса main thread, который мы не хотим блокировать. Также мы
      // даем создаваемому потоку background-приоритет, чтобы он не грузил
      // CPU и не тормозил тем самым работу UI нашего приложения.
      HandlerThread thread = new HandlerThread("ServiceStartArguments",
         Process.THREAD_PRIORITY_BACKGROUND);
      thread.start();
 
      // Получение HandlerThread Looper и использование его для нашего
      // обработчика:
      serviceLooper = thread.getLooper();
      serviceHandler = new ServiceHandler(serviceLooper);
   }
 
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
 
      // Для каждого запроса на запуск отправьте сообщение, чтобы запустить
      // задание и передать идентификатор запуска (startID), чтобы мы
      // знали, какой запрос останавливаем, когда задание завершено:
      Message msg = serviceHandler.obtainMessage();
      msg.arg1 = startId;
      serviceHandler.sendMessage(msg);
 
      // Если нас прибьют, то после возвращения отсюда делаем restart.
      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();
  }
}

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

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

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

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

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

Более подпробно про эти значения возврата см. справочное руководство по каждой константе.

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

Замечание: если у вашего приложения целевое API level 26 или более новое, то система накладывает ограничнеия на использование или создание background сервисов, если только само приложение на находится на переднем плане (foreground). Если приложению нужно создать foreground-службу, оно должно вызвать startForegroundService(). Этот метод создаст background-службу, однако метод сигнализирует системе, что служба представляет себя для работы в foreground. Как только служба создана, она должна вызвать свой метод startForeground() в течение 5 секунд.

Например, activity может запустить пример службы из предыдущей секции (HelloService), испльзуя explicit intent с startService(), ниже показаны примеры.

Пример Kotlin:

Intent(this, HelloService::class.java).also { intent ->
   startService(intent)
}

То же самое на Java:

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

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

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

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

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

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

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

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

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

[Создание привязываемого (bound) сервиса]

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

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

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

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

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

Есть несколько способов реализации bound-службы, и её реализация сложнее, чем у запускаемой службы. По этой причине описание работы с bound-службой вынесено в отдельный документ [10].

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

Когда служба работает, она может оповещать пользователя о событиях, используя "оповещения закусочной" (snackbar notifications) или оповещения в полосе статуса (status bar notifications).

Snackbar notification это сообщение, которое появляется на поверхности текущего окна только на некоторое время, после чего пропадает. Status bar notification предоставляет иконку с сообщением на полосе статуса, которое пользователь может выбрать для предпринятия действия (такого как запуск activity).

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

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

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

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

· Started service (запускаемая служба)

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

· Bound service (привязываемая служба)

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

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

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

// Код Kotlin:
class ExampleService : Service() {
   private var startMode: Int = 0           // показывает поведение, когда служба прибита
   private var binder: IBinder? = null      // интерфейс для привязки клиентов
   private var allowRebind: Boolean = false // указывает, должен ли использоваться onRebind
 
   override fun onCreate() {
      // Служба создается.
   }
 
   override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
      // Служба запустилась вызовом startService().
      return startMode
   }
 
   override fun onBind(intent: Intent): IBinder? {
      // Клиент привязывается к службе вызовом bindService().
      return binder
   }
 
   override fun onUnbind(intent: Intent): Boolean {
      // Все клиенты отвязались с помощью вызова unbindService().
      return allowRebind
   }
 
   override fun onRebind(intent: Intent) {
      // Клиент повторно привязывается к службе вызовом bindService()
      // после того, как уже был вызван onUnbind().
   }
 
   override fun onDestroy() {
      // Служба больше не используется, и уничтожается системой.
   }
}

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

Замечание: в отличие от callback-методов цикла жизни activity, вам не требуется вызывать реализацию суперкласса этих callback-методов.

service lifecycle fig01

Рис. 1. Цикл жизни службы. Диаграмма слева показывает цикл жизни, когда служба создана через startService(), и диаграмма справа - когда служба создана с помощью bindService().

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

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

Entire lifetime (полное время жизни) службы это время от момента, когда вызван callback-метод onCreate(), и до момента, когда произведен возврат из callback-метода onDestroy(). Наподобие activity, служба делает свои первоначальные настройки в вызове onCreate(), и освобождает все выделенные ресурсы в onDestroy(). Например, служба прогрывания в onCreate() может создать поток, который проигрывает музыку, и остановит этот поток в onDestroy().

Замечание: методы onCreate() и onDestroy() вызываются для всех служб независимо от того, как они были созданы - через startService() или bindService().

Active lifetime (время активности) начинается с момента, когда произошел вызов или onStartCommand(), или onBind(). Каждому из этих методов вручается Intent, который был передан либо в startService(), либо в bindService().

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

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

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

[Краткий справочник по API]

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

getBroadcast Получение широковещательной передачи от службы.

onBind Callback для службы, когда к ней выполнил привязку компонент приложения.

onCreate Callback для службы, который запускается в момент её создания.

onDestroy Callback для службы, который запускается в момент её уничтожения.

onStartCommand Callback для службы, который вызывается, когда служба запущена функцией startService().

onStart Callback-метод активности для момента, когда она запускается (не относится к службе).

onStop Callback-метод активности для момента, когда она оставливается (не относится к службе).

onUnbind Callback события отключения клиента от службы.

startForeground Функция для активации поведения переднего плана службы.

startForegroundService Функция, которая запустит службу, которая может работать на переднем плане UI.

startService Функция для запуска службы.

stopSelf Функция, с помощью которой служба может остановить саму себя.

stopService Функция, с помощью которой службу может остановить компонент приложения.

unbindService Функция, с помощью которой компонент приложения отвязывается от службы.

[Ссылки]

1. Android Services overview site:developer.android.com.
2. Android Notifications overview site:developer.android.com.
3. Android Foreground services site:developer.android.com.
4. Android Intent site:developer.android.com.
5. Android Thread site:developer.android.com.
6. Kotlin coroutines on Android site:developer.android.com.
7. Android Processes and threads overview site:developer.android.com.
8. Things That Cannot Change site:android-developers.googleblog.com.
9. Android Background Work Overview site:developer.android.com.
10. Android Bound services overview site:developer.android.com.