Как работают сервисы в Android и как их использовать

Сервисы — это компоненты для фоновой работы без UI: используйте startService/startForegroundService для длительных задач и bindService/ServiceConnection для прямого взаимодействия. Remote Service (AIDL) применяется при необходимости IPC между процессами. Ниже — практическое описание, шаблоны кода и советы, чтобы сразу применить.

Что такое Service и какие бывают

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

  • Started Service (запуск через startService/startForegroundService): работает независимо от активностей. Жизненный цикл: onCreate → onStartCommand → onDestroy. Используйте startForeground() + уведомление на Android 8+ для устойчивости.
  • Bound Service (привязка через bindService): предоставляет интерфейс для взаимодействия (обычно через Binder). Живёт, пока есть привязанные клиенты.
  • Remote Service (отдельный процесс, AIDL): для IPC между приложениями/модулями или изоляции тяжёлых/критичных операций.

Краткий пример started+bound (Kotlin):

class MyService : Service() {
  private val binder = MyBinder()
  inner class MyBinder : Binder() { fun getService() = this@MyService }
  override fun onBind(intent: Intent?) = binder
  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = START_STICKY
}

Регистрация в AndroidManifest:

<service android:name=".MyService" />

Для периодических задач предпочтительнее WorkManager (учитывает Doze и ограничения батареи). Service — когда нужен длительный foreground-процесс или немедленное непрерывное исполнение.

ServiceConnection: как правильно привязываться и общаться

ServiceConnection — интерфейс, через который клиент получает IBinder для вызова методов сервиса. Типичный паттерн для локальной привязки:

private var service: MyService? = null
private val connection = object : ServiceConnection {
  override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
    val myBinder = binder as MyService.MyBinder
    service = myBinder.getService()
  }
  override fun onServiceDisconnected(name: ComponentName?) {
    service = null
  }
}
override fun onStart() {
  bindService(Intent(this, MyService::class.java), connection, Context.BIND_AUTO_CREATE)
}
override fun onStop() {
  unbindService(connection)
}

Практические рекомендации:

  • Всегда делать unbindService в onStop/onDestroy, чтобы избежать утечек.
  • Для асинхронных сообщений между процессами используйте Messenger вместо AIDL, если интерфейс прост.
  • Не блокируйте UI-поток внутри сервиса — используйте Coroutine, Executor или IntentService/JobIntentService.

Remote Service (AIDL): когда и как внедрять

Remote Service нужен для IPC: разные процессы, модульная архитектура, защита критичных данных. Шаги:

  1. Создайте .aidl-файл (интерфейс). Пример IMyService.aidl:
   package com.example;
   interface IMyService {
     String getData();
   }
  1. Система сгенерирует Stub/Proxy. В сервисе реализуйте Stub и возвращайте его в onBind():
   private val stub = object : IMyService.Stub() {
     override fun getData(): String = "ok"
   }
   override fun onBind(intent: Intent?) = stub.asBinder()
  1. Клиент через ServiceConnection получает прокси и вызывает удалённые методы.

Remote Service повышает расход памяти и батареи, сложнее отлаживать и требует сериализации Parcelable. Используйте только при реальной необходимости IPC.

Частые ошибки

  • Не вызывать unbindService — утечки памяти.
  • Забудьте вызвать startForeground() на Android 8+ для длительных background задач — сервис будет убит.
  • Использовать AIDL для простых сообщений — перегрузка. Выберите Messenger или BroadcastReceiver.
  • Игнорировать права (BIND_SERVICE) и intent-filter security.
  • Блокировать главный поток в методах сервиса.

FAQ

  • Когда выбирать startService vs bindService?
    • startService — если задача должна выполняться независимо от UI. bindService — если нужен прямой доступ к методам сервиса.
  • Как корректно остановить сервис?
    • Для started service: stopSelf() или stopService(intent). Для bound service — unbindService; сервис завершится, если нет других клиентов и не был стартован.
  • Как протестировать поведение при оптимизации батареи?
    • Тестируйте на реальных устройствах с включённой оптимизацией батареи и в Doze-режиме; используйте adb для симуляции Doze.
  • Можно ли обращаться к UI из сервиса?
    • Нет. Сервис работает в фоновом потоке — взаимодействуйте через Broadcast/Handler/LiveData или отправляйте интенты/уведомления.

Если нужно, могу подготовить компактный шаблон Service + Foreground Notification или пример AIDL с Parcelable.