Service

https://developer.android.com/about/versions/14/changes/fgs-types-required
https://developer.android.com/about/versions/15/changes/foreground-service-types
https://developer.android.com/reference/android/app/DownloadManager
16.06.2023https://habr.com/ru/companies/broadcast/articles/734236/
14.02.2018https://habr.com/ru/post/349102
28.06.2024https://youtu.be/DgCmmL4FNAs
16.06.2023https://youtu.be/Ovzc2kaeSCY
Service

Компонент для выполнения длительных операций в фоновом режиме.

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

• Сервисы нужны, чтобы связать их lifecycle с lifecycle приложения, так как обычные потоки не всегда совпадут с activity lifecycle (+возможность запуститься не только из activity).

• Как работает: в момент создания сервиса вызывается onCreate, затем запускается thread или executor, который после завершения дает знать сервису, чтобы тот вызвал stopSelf. Наша задача: сообщить сервису о начале и завершении работы.

• Чтобы сервис работал после закрытия activity нужно в течение 5 секунд после его запуска отобразить уведомление, иначе ANR.

stopSelf - завершить работу сервиса.

Intent.stopService - завершить работу сервиса через intent, вызовется stopSelf - onDestroy.

onCreate

Вызывается один раз при создании сервиса.

override fun onCreate() {
    super.onCreate()
    Log.d("MyService", "Сервис создан")
}
onStartCommand

Вызывается каждый раз, когда сервис запускается через startService. Возвращает код, указывающий поведение сервиса при его перезапуске:

START_NOT_STICKY: сервис не перезапускается после уничтожения.

START_STICKY: сервис перезапускается, но с nullинтентом.

START_REDELIVER_INTENT: сервис перезапускается с последним интентом.

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    val data = intent?.getStringExtra("key")
    Log.d("MyService", "Получены данные: $data")
    return START_STICKY
}
onDestroy

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

override fun onDestroy() {
    super.onDestroy()
    Log.d("MyService", "Сервис уничтожен")
}
Запуск в отдельном процессе

Чтобы запустить сервис в отдельном процессе нужно указать в манифесте:

android:process=":someProcess"

Плюсы:

• Кейс с пушами. если приложение не работает, при получении пуша не вызывался App.onCreate.

• Независимость от ЖЦ приложения, если упадет сможем закончить работу (например библиотека с крашлитикой).

• Больше ресурсов, например если хотим вынести тяжелую операцию из основного процесса.

Минусы:

• Напрямую привязаться к сервису в другом процессе можно только через AIDL.

Background Service

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

• Начиная с API 26 (Android 8.0) приложения не могут вызывать startService, если они не находятся на переднем плане. Если попытаться запустить фоновый сервис из фона, он будет немедленно завершён системой.

• Начиная с API 26 (Android 8.0) вместо startService, приложения должны использовать startForegroundService для запуска сервисов в фоне.

• Используй WorkManager для фоновых операций, не требующих немедленного выполнения.

Foreground Service

Сервис, о котором пользователь уведомлен. Выполняется в UI thread. Во время выполнения отображает уведомление. Пример: аудиоплеер. Приоритет больше, чем background.

stopForeground завершит выполнение сервиса на переднем плане и опционально удалит уведомление.

Foreground Service Type

Этот атрибут дает понять системе к какой категории отнести сервис. Указывается внутри элемента <service> в AndroidManifest. Можно комбинировать несколько типов.

• Android 10 (API 29) - добавлен необязательный атрибут android:foregroundServiceType.

• Android 14 (API 34) - указание типа стало обязательным. Если приложение предназначенное для Android 14 не определяет типы для данной службы в AndroidManifest система выкинет MissingForegroundServiceTypeException при вызове startForeground. Также нужно объявить определенные permissions на основе типа сервиса иначе получим SecurityException.

<service
    android:name=".MyMediaPlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="false">
</service>
Foreground service typeОписаниеPermission in AndroidManifestПоявился
cameraдоступ к камере, видеочатFOREGROUND_SERVICE_CAMERAAndroid 10
connectedDeviceустройства, фитнес-трекерFOREGROUND_SERVICE_CONNECTED_DEVICEAndroid 10
dataSyncсинхронизация данныхFOREGROUND_SERVICE_DATA_SYNCAndroid 10
locationдоступ к локации, навигацияFOREGROUND_SERVICE_LOCATIONAndroid 10
mediaPlaybackвоспроизведение аудиоFOREGROUND_SERVICE_MEDIA_PLAYBACKAndroid 10
mediaProjectionпроекция контентаFOREGROUND_SERVICE_MEDIA_PROJECTIONAndroid 10
microphoneдиктофонFOREGROUND_SERVICE_MICROPHONEAndroid 10
phoneCallтелефонный звонокFOREGROUND_SERVICE_PHONE_CALLAndroid 10
healthфитнес, трекер тренировокFOREGROUND_SERVICE_HEALTHAndroid 14
remoteMessagingобмен смсFOREGROUND_SERVICE_REMOTE_MESSAGINGAndroid 14
shortServiceтаска на 3 мин-Android 14
specialUseлюбая таскаFOREGROUND_SERVICE_SPECIAL_USEAndroid 14
systemExemptedзарезервировано для системыFOREGROUND_SERVICE_SYSTEM_EXEMPTEDAndroid 14
Bound Service

Особый тип сервиса в Android, который позволяет другим компонентам (например, Activity) взаимодействовать с ним через интерфейс IBinder. Он предоставляет механизм для клиентов, чтобы подключаться к сервису и выполнять операции на его экземпляре.

• В отличие от обычных Started Services, которые могут работать независимо от клиентских запросов, Bound Services существуют до тех пор, пока хотя бы один клиент к ним привязан.

• Как только все клиенты отключаются, сервис может быть автоматически остановлен системой.

• С помощью IBinder интерфейса клиент может взаимодействовать с сервисом. Это позволяет клиенту вызывать методы сервиса напрямую.

class MyBoundService: Service() {

    // Класс для взаимодействия с клиентами
    private val binder = LocalBinder()

    // Метод для получения экземпляра сервиса
    inner class LocalBinder: Binder() {
        fun getService(): MyBoundService = this@MyBoundService
    }

    override fun onBind(intent: Intent?): IBinder? {
        return binder
    }

    // Пример метода, доступного клиентам
    fun getData(): String {
        return "Some data"
    }
}
// Привязка к Bound Service из Activity

class MainActivity: AppCompatActivity() {
    private var myService: MyBoundService? = null
    private var isBound = false
    
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val binder = service as MyBoundService.LocalBinder
            myService = binder.getService()
            isBound = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            isBound = false
        }
    }

    override fun onStart() {
        super.onStart()
        // Привязка к сервису
        Intent(this, MyBoundService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        // Отвязка от сервиса
        if (isBound) {
            unbindService(connection)
            isBound = false
        }
    }

    fun useService() {
        if (isBound) {
            val data = myService?.getData()
            // Использовать данные
        }
    }
}
IBinder

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

class MyService: Service() {
    private val binder = LocalBinder()

    override fun onBind(intent: Intent?): IBinder {
        return binder
    }

    inner class LocalBinder : Binder() {
        fun getService(): MyService = this@MyService
    }

    fun performAction(): String {
        return "Действие выполнено!"
    }
}
onBind

Вызывается, когда клиент пытается подключиться к сервису через bindService. Он возвращает объект IBinder, который клиент использует для взаимодействия с сервисом.

class MyService: Service() {
    private val binder = LocalBinder()

    override fun onBind(intent: Intent?): IBinder {
        Log.d("MyService", "Клиент подключён")
        return binder
    }

    inner class LocalBinder : Binder() {
        fun getService(): MyService = this@MyService
    }
}
onUnbind

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

override fun onUnbind(intent: Intent?): Boolean {
    return true
}
IntentService

Класс для выполнения однократных фоновых задач в отдельном потоке. Упрощает работу с многопоточностью, автоматически завершает себя после выполнения задачи.

JobScheduler

API для планирования фоновых задач, оптимизированное для энергосбережения.

• Появился в API 21 (Android 5.0).

JobIntentService

Класс для выполнения фоновых задач, совместимый с API ниже 26. Он сочетает преимущества IntentService и JobScheduler.

AlarmManager

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

DownloadManager

Системный сервис для загрузки файлов в фоновом режиме.

WakeLock

Механизм для управления энергопотреблением. Он позволяет приложениям удерживать устройство в активном состоянии, предотвращая переход в спящий режим.

Вопросы на собесе (18)
  1. С чем связаны ограничения на запуск сервисов?

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

  1. Какие бывают типы сервисов?

    Foreground Service (видим через уведомление)

    Background Service (не взаимодействует с пользователем)

    Bound Service (можно привязаться из другого компонента и взаимодействовать с ним через IBinder, может быть как Foreground так и Background)

  1. Зачем сделали Foreground Service?

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

  1. Как запустить сервис в отдельном процессе?

    Указать атрибут android:process в манифесте.

  1. Разница между Service и IntentService?

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

  1. Что такое Bound-сервис?

    Bound Service позволяет компонентам, таким как Activity, связываться с сервисом и вызывать его методы через интерфейс. Это обеспечивает более тесное взаимодействие между сервисом и компонентами приложения, что полезно для обмена данными или выполнения задач по запросу.

  1. Разница между обычным Service и Bound Service?

    Обычный Service выполняет длительные операции в фоновом режиме и не предоставляет интерфейса для взаимодействия с компонентами приложения. Bound Service, в отличие от него, позволяет компонентам, таким как Activity, связываться с сервисом и вызывать его методы через интерфейс. Это обеспечивает более тесное взаимодействие между сервисом и компонентами приложения, что полезно для обмена данными или выполнения задач по запросу.

  1. Как сделать обмен данных между Activity и Service?

    Использовать Bound Service.

    Используя Bound Service и AIDL. Используется для более сложного и высокоуровневого обмена данными, особенно когда Service и Activity находятся в разных процессах.

    Через BroadcastReceiver. Он позволяет отправлять и получать данные между компонентами. Подходит для простых случаев, когда необходимо отправить данные от Service к Activity или наоборот.

    Через Messenger и IncomingHandler. Подходит для обмена более структурированными данными и для взаимодействия между Activity и Service в том же процессе.

  1. Зачем нужен метод onBind?

    Используется для связывания Activity с сервисом. Он возвращает объект IBinder, который позволяет Activity взаимодействовать с сервисом, вызывая его методы напрямую. Это необходимо для реализации "bound services", где компонент может взаимодействовать с сервисом, пока он запущен.

  1. Какое исключение возникает при долгой работе сервиса?

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

  1. Какие есть способы запуска сервисов?

    startService() Запускает сервис для фоновой работы.

    bindService() Связывает компонент с сервисом для взаимодействия.

    IntentService Автоматически завершает себя после выполнения задачи.

    JobIntentService Выполняет задачи с учетом ограничений (сеть, заряд).

  1. Жизненный цикл сервиса?

    Различается для обычных и Bound-сервисов.

    Для обычных: onCreate onStartCommand onDestroy.

    Для Bound: onCreate onStartCommand onBind onUnbind onDestroy.

  1. На каком потоке вызывается метод Service.onStartCommand?

    На клавном (UI) потоке приложения.

  1. Как работает межпроцессорная коммуникация в сервисах?

    Binder Основной механизм IPC в Android. Позволяет процессам обмениваться данными и вызывать методы друг друга. Сервис может возвращать объект Binder, чтобы другие компоненты могли взаимодействовать с ним.

    Messenger Обертка над Binder, которая использует Message для отправки и получения данных. Удобно для простых взаимодействий между компонентами.

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

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

  1. Есть ли смысл использовать сервис если можно выполнять работу в Application.onCreate?

    Использовать сервис имеет смысл, если работа должна выполняться в фоновом режиме, даже когда приложение закрыто, чего нельзя достичь через Application.onCreate.

  1. Какие есть альтернативы сервисам в Android?

    WorkManager — используется для фоновых задач, которые должны гарантированно выполняться, например, синхронизация данных, даже если приложение закрыто.

    JobScheduler — подходит для задач, которые должны быть запущены при выполнении определённых условий, таких как подключение к сети или зарядка устройства.

  1. Какой из следующих методов используется для запуска сервисов в Android 8.0 и выше?

    startForegroundService()

    runService()

    startService()

    bindService()

  1. На каком потоке по умолчанию работает Service?

    MainThread

    ServiceThread

    NewThread

    ApplicationThread