Ответ
class MyService : Service() {
private val binder = LocalBinder()
override fun onBind(intent: Intent): IBinder {
return binder
}
inner class LocalBinder : Binder() {
fun getService(): MyService = this@MyService
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Выполняем фоновую работу
return START_STICKY
}
}
// Запуск сервиса из Activity
val serviceIntent = Intent(this, MyService::class.java)
startService(serviceIntent) // Для started service
// или
bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE) // Для bound service
Сервис работает в главном потоке, для долгих операций нужно использовать IntentService или WorkManager. START_STICKY перезапускает сервис после убийства системы. Для связи с Activity используется Binder. Не забудьте объявить сервис в манифесте.
Ответ 18+ 🔞
А, ну это же классика, ёпта! Смотри, как тут всё устроено, прям хитрая жопа, но если разобраться — ничего сложного.
Вот смотри, есть у тебя этот MyService, наследник от Service(). Внутри у него приватный binder — это типа его внутренняя кухня, LocalBinder. И когда система спрашивает: «А дай-ка мне твой интерфейс для связи», сервис в методе onBind просто выкатывает этого самого binder со словами «На, общайся».
А в этом binder-е, в его внутреннем классе, есть функция getService(). Она, сука, просто возвращает ссылку на сам внешний сервис (this@MyService). Вот и весь фокус! Activity через эту штуку может достучаться до методов сервиса напрямую. Удобно, да?
inner class LocalBinder : Binder() {
fun getService(): MyService = this@MyService
}
Дальше, смотри на onStartCommand. Вот тут как раз та самая точка входа, когда сервис запускают как started service. Он возвращает START_STICKY. Это что значит? А это значит, что если система от нехватки памяти прибьёт твой сервис, как только ресурсы появятся — она его воскресит, но с пустым интентом (intent = null). Типа «извини, братан, пришлось замочить, но вот тебе новая жизнь, живи». Будет вам хиросима, если ты на это не рассчитывал.
А вот как это всё из Activity запускается — вообще ебушки-воробушки:
val serviceIntent = Intent(this, MyService::class.java)
startService(serviceIntent) // Для started service
// или
bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE) // Для bound service
Первым способом (startService) ты запускаешь его в фоне, и он будет работать, пока сам не остановится или ты его не прикончишь. Второй способ (bindService) — это когда Activity привязывается к сервису, чтобы с ним болтать через тот самый Binder. Как только все Activity отвяжутся — сервис может и помереть (если он не был ещё и запущен через startService).
И вот тут, чувак, главный подвох, на котором все обжигаются! Этот сервис, блядь, работает в ГЛАВНОМ ПОТОКЕ! Если ты в onStartCommand или в методах, которые вызывает Activity, вздумаешь спать 10 секунд или сетевой запрос тянуть — интерфейс встанет колом. Удивление пиздец будет у пользователя. Для долгой работы нужно либо внутри сервиса свой поток запускать (геморрой), либо использовать IntentService (он сам в отдельном потоке работает, но его уже deprecated, осторожно), либо, что сейчас модно, — WorkManager. Это чтобы не получить в тапки от системы.
И да, самый важный момент, который все забывают, а потом орут «не работает!». Сервис-то в манифест надо прописать! А то будет он у тебя как призрак, в коде есть, а в системе — хуй с горы.
<application ...>
<service android:name=".MyService" />
</application>
Вот и вся магия. Связь через Binder, для фона — отдельные потоки, флаги жизненного цикла понимай, и в манифест не забудь. А то будет не сервис, а одна мудня какая-то.