|
1 | 1 | # Сервисы |
2 | 2 |
|
3 | | -## Android Service Component (in progress) |
| 3 | +## Android Service Component |
| 4 | +[Services overview](https://developer.android.com/guide/components/services) |
4 | 5 |
|
5 | | -[Services overview](https://developer.android.com/guide/components/services). |
| 6 | +### Что такое Service |
| 7 | +Сервис — один из компонентов Android для выполнения длительных операций без UI. |
6 | 8 |
|
7 | | -### Виды сервисов |
| 9 | +❗ Важно: |
| 10 | +- Сервис **не создаёт отдельный поток** |
| 11 | +- Все lifecycle-методы вызываются на **main thread** |
| 12 | +- Долгую работу нужно выполнять в отдельном потоке (Coroutine, Executor и т.д.) |
8 | 13 |
|
9 | | -Сервисы - один из компонентов андроида. Все сервисы деляться на два вида: [Foreground](https://developer.android.com/guide/components/foreground-services) и Background. |
| 14 | +Сервис является точкой входа в приложение. |
10 | 15 |
|
11 | | -Foreground - сервис выполняет функционал заметный для пользователя, и может продолжать работу даже если пользователь свернул приложение. Например проигрывать музыку, или отображать плавающее окошко с видео. Сервис обязан показать плашку уведомления о том, что он работает, это уведомление нельзя убрать пока сервис запущен. |
12 | | -Для использования Foreground сервиса требуется специальный пермишен |
| 16 | +При нехватке памяти система в первую очередь избавляется от процессов с более низким приоритетом. Подробнее про приоритеты процессов: |
| 17 | +[Who lives and who dies — process priorities on Android](https://medium.com/androiddevelopers/who-lives-and-who-dies-process-priorities-on-android-cb151f39044f) |
| 18 | + |
| 19 | +## Виды сервисов |
| 20 | + |
| 21 | +Сервисы делятся на два основных типа: |
| 22 | + |
| 23 | +### Foreground Service |
| 24 | +[Foreground services](https://developer.android.com/guide/components/foreground-services) |
| 25 | + |
| 26 | +Foreground — сервис выполняет функционал, заметный пользователю, и может продолжать работу, даже если приложение свернуто. |
| 27 | + |
| 28 | +Примеры: |
| 29 | +- проигрывание музыки |
| 30 | +- навигация |
| 31 | +- трекинг геолокации |
| 32 | +- VoIP-звонки |
| 33 | +- запись аудио/видео |
| 34 | +- плавающее окно с видео |
| 35 | + |
| 36 | +Обязательные требования: |
| 37 | + |
| 38 | +- Сервис обязан показать уведомление |
| 39 | +- Уведомление нельзя убрать, пока сервис работает |
| 40 | +- Необходимо разрешение: |
13 | 41 |
|
14 | 42 | ```xml |
15 | 43 | <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> |
16 | 44 | ``` |
17 | 45 |
|
18 | | -И foreground и Background сервисы запускаются на main потоке |
| 46 | +Начиная с Android 10+ необходимо указывать тип сервиса: |
| 47 | + |
| 48 | +```xml |
| 49 | +<service |
| 50 | + android:name=".LocationService" |
| 51 | + android:foregroundServiceType="location" |
| 52 | + android:exported="false" /> |
| 53 | +``` |
| 54 | + |
| 55 | +В Android 14+ для некоторых типов требуется отдельное разрешение, например: |
| 56 | + |
| 57 | +```xml |
| 58 | +<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/> |
| 59 | +``` |
| 60 | + |
| 61 | +Примеры типов: |
| 62 | +- location |
| 63 | +- mediaPlayback |
| 64 | +- camera |
| 65 | +- microphone |
| 66 | +- dataSync |
| 67 | +- connectedDevice |
| 68 | +- phoneCall |
| 69 | + |
| 70 | +Начиная с Android 12 действуют ограничения на запуск Foreground Service из background — система может запретить запуск. |
| 71 | + |
| 72 | +### Background Service |
| 73 | + |
| 74 | +Background — сервис выполняет функционал, скрытый от пользователя. |
| 75 | + |
| 76 | +⚠ Начиная с API 26 (Android 8.0) действуют ограничения: |
| 77 | +- Приложение не может свободно запускать background service, если находится в background |
| 78 | +- Возможен `IllegalStateException` |
| 79 | + |
| 80 | +В большинстве случаев вместо background service рекомендуется использовать: |
| 81 | +- ✅ [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) |
| 82 | +- ✅ JobScheduler |
| 83 | +- ✅ AlarmManager (ограниченно) |
| 84 | + |
| 85 | +Background Service в современных приложениях используется редко. |
| 86 | + |
| 87 | +## Потоки |
| 88 | + |
| 89 | +И Foreground, и Background сервисы запускаются на main потоке. |
| 90 | + |
| 91 | +❗ Если выполнять долгую операцию внутри `onStartCommand()`, можно получить ANR. |
| 92 | + |
| 93 | +Правильный подход: |
| 94 | +- Coroutine + Dispatchers.IO |
| 95 | +- Thread / Executor |
| 96 | +- WorkManager |
| 97 | + |
| 98 | +## IntentService и JobIntentService |
| 99 | + |
| 100 | +[IntentService](https://developer.android.com/reference/android/app/IntentService.html) — **deprecated** |
| 101 | +[JobIntentService](https://developer.android.com/reference/androidx/core/app/JobIntentService.html) — **deprecated** |
| 102 | + |
| 103 | +Ранее они автоматически выполняли работу в отдельном потоке. |
| 104 | +Сейчас рекомендуется использовать: |
| 105 | +- WorkManager |
| 106 | +- Foreground Service + coroutine |
| 107 | + |
| 108 | +## Объявление в манифесте |
| 109 | + |
| 110 | +Независимо от типа сервис должен быть объявлен в манифесте: |
| 111 | + |
| 112 | +```xml |
| 113 | +<service |
| 114 | + android:name=".MyService" |
| 115 | + android:exported="false"/> |
| 116 | +``` |
| 117 | + |
| 118 | +Начиная с Android 12 (targetSdk 31+) необходимо явно указывать `android:exported`, если есть `intent-filter`. |
| 119 | + |
| 120 | +- exported="true" — сервис доступен другим приложениям |
| 121 | +- exported="false" — только внутри приложения |
| 122 | + |
| 123 | +## Запуск сервиса |
| 124 | + |
| 125 | +Сервис можно запустить двумя способами: |
| 126 | + |
| 127 | +### startService() |
| 128 | + |
| 129 | +```kotlin |
| 130 | +startService(intent) |
| 131 | +``` |
| 132 | + |
| 133 | +Если приложение в background (API 26+), необходимо использовать: |
| 134 | + |
| 135 | +```kotlin |
| 136 | +ContextCompat.startForegroundService(...) |
| 137 | +``` |
| 138 | + |
| 139 | +После этого нужно вызвать `startForeground()` в течение ~5 секунд. |
| 140 | + |
| 141 | +Жизненный цикл started service: |
| 142 | + |
| 143 | +- onCreate() |
| 144 | +- onStartCommand() |
| 145 | +- onDestroy() |
| 146 | + |
| 147 | +Возвращаемое значение `onStartCommand()` влияет на перезапуск сервиса: |
| 148 | +- START_NOT_STICKY |
| 149 | +- START_STICKY |
| 150 | +- START_REDELIVER_INTENT |
| 151 | + |
| 152 | +Сервис будет жить, пока не вызван: |
| 153 | +- stopSelf() |
| 154 | +- stopService() |
| 155 | + |
| 156 | +### bindService() |
| 157 | + |
| 158 | +```kotlin |
| 159 | +bindService(...) |
| 160 | +``` |
| 161 | + |
| 162 | +При запуске через bindService() сервис будет жить, пока у него есть хотя бы один клиент. |
| 163 | +Когда все клиенты отпишутся — сервис будет уничтожен. |
| 164 | + |
| 165 | +Жизненный цикл у bound service: |
| 166 | + |
| 167 | +- onCreate() |
| 168 | +- onBind() |
| 169 | +- onUnbind() |
| 170 | +- onRebind() |
| 171 | +- onDestroy() |
| 172 | + |
| 173 | +Если сервис был запущен через startService(), на него всё равно можно подписаться: |
| 174 | +[Bound services](https://developer.android.com/guide/components/bound-services) |
| 175 | + |
| 176 | +Сервис может быть одновременно started и bound. |
| 177 | +Он уничтожится, только когда: |
| 178 | +- вызван stopSelf() |
| 179 | +- и нет активных bind-подключений |
19 | 180 |
|
20 | | -Background - сервис выполняет функционал скрытый от пользователя. Начиная с API-26 фоновые сервисы имеют некоторые ограничения при свернутом приложении, например они не могут получить геолокацию |
| 181 | +Жизненный цикл сервиса не зависит от жизненного цикла компонента, который его запустил. |
21 | 182 |
|
22 | | -Хотя [IntentService](https://developer.android.com/reference/android/app/IntentService.html) и запускается на главном потоке, но для обработки onHandleIntent он запускает отдельный поток. Аналогично [JobIntentService](https://developer.android.com/reference/androidx/core/app/JobIntentService.html) обрабатывает enqueueWork на отдельном потоке |
| 183 | +## Обращение к сервису из другого процесса |
23 | 184 |
|
24 | | -Не зависимо от вида сервиса он должен быть объявлен в манифесте приложения |
| 185 | +Для взаимодействия с сервисом из другого процесса используется: |
| 186 | +- [AIDL](https://developer.android.com/guide/components/aidl) |
| 187 | +- Messenger |
| 188 | +- Binder |
25 | 189 |
|
26 | | -Как и любой андроид компонент сервис является точкой входа в приложение, помимо этого при нехватке памяти в первую очередь система избавится от приложений [не имеющих сервисы](https://medium.com/androiddevelopers/who-lives-and-who-dies-process-priorities-on-android-cb151f39044f) |
| 190 | +## Когда стоит использовать Service |
27 | 191 |
|
28 | | -### Запуск сервиса |
| 192 | +✅ Foreground Service — если: |
| 193 | +- работа длительная |
| 194 | +- пользователь понимает, что она выполняется |
| 195 | +- требуется повышенный приоритет процесса |
29 | 196 |
|
30 | | -Сервис можно запустить двумя способами: через startService() или через bindService() |
| 197 | +✅ WorkManager — если: |
| 198 | +- задача должна гарантированно выполниться |
| 199 | +- задача может быть отложенной |
| 200 | +- нужна работа после перезапуска устройства |
31 | 201 |
|
32 | | -Разница состоит в том, что при запуске через bindService() сервис будет жить до тех пор пока у него есть хотя бы один подписчик, после того как от сервиса отпишутся все подписчики он будет уничтожен. При запуске через startService() сервис будет жить не зависимо от количества подписчиков, пока не будет вызвана команда для его остановки. |
33 | | -Жизненный цикл сервиса не зависит от жизненного цикла компонента который его запустил. |
34 | | -Если сервис был запущен через startService(), на него все равно можно [подписаться](https://developer.android.com/guide/components/bound-services) |
| 202 | +В большинстве фоновых задач предпочтительнее WorkManager. |
35 | 203 |
|
36 | | -### Обращение к сервису из другого процесса |
| 204 | +## Главное помнить |
37 | 205 |
|
38 | | -Обращение к сервису из другого процесса возможно, для этого следует использовать [Android Interface Definition Language ](https://developer.android.com/guide/components/aidl) |
| 206 | +- Service не создаёт поток |
| 207 | +- IntentService устарел |
| 208 | +- Background Service сильно ограничены с Android 8+ |
| 209 | +- Foreground Service строго регулируется на Android 12+ |
| 210 | +- В большинстве случаев для фоновой работы лучше использовать WorkManager |
39 | 211 |
|
40 | 212 | ### Примеры использования сервисов |
41 | 213 |
|
42 | 214 | [Сервис для скачивания файлов](https://gitlab.icerockdev.com/marmalato/marmalato-android/-/blob/develop/app/src/main/java/com/icerockdev/marmalato/feature/loader/LoaderService.kt) |
43 | 215 | Сервис в своем потоке скачивает файлы, может сообщать процент скачивания для отображения прогресса в активити |
44 | 216 |
|
45 | 217 | [Стартовый сервис helga](https://gitlab.icerockdev.com/helga/helga-client/-/blob/dev/client-service/src/main/java/com/icerockdev/helga/client/service/HelgaService.kt) |
46 | | -Сервис проверяет авторизацию пользователя, запускает остальные сервисы приложения(ожидание звонков, сервис для нотификаций календаря и др.) |
| 218 | +Сервис проверяет авторизацию пользователя, запускает остальные сервисы приложения (ожидание звонков, сервис для нотификаций календаря и др.) |
47 | 219 |
|
48 | 220 | [Сервис для приема входящих звонков](https://gitlab.icerockdev.com/helga/helga-client/-/blob/dev/client-contacts/src/main/java/com/icerockdev/helga/client/contacts/feature/invitation/CallInvitationService.kt) |
49 | 221 | При входящем звонке показывает плавающее окно-уведомление |
|
0 commit comments