Coroutines — это легковесные потоки, которые позволяют выполнять асинхронные операции без блокировки основного потока. Они предоставляют механизм для приостановки и возобновления выполнения кода.
Корутины в Kotlin
иногда называют легковесными потоками, потому что они позволяют выполнять задачи параллельно или асинхронно, не создавая при этом тяжелых системных потоков (threads
).
- Упрощение кода: Нет
callback hell
, код выглядит как синхронный. - Интеграция с жизненным циклом: Использование
lifecycleScope
для автоматической отмены корутин. - Гибкость: Поддержка параллельных запросов, таймаутов и других сложных сценариев.
Suspend — это ключевое слово, которое указывает, что функция может быть приостановлена и возобновлена.
Suspend
-функции могут вызываться только из других suspend
-функций или корутин.
Функция может приостановить своё выполнение в определённой точке и вернуть результат или выбросить Exception
.
Continuation — это объект, который представляет собой точку возобновления выполнения корутины.
Когда корутина приостанавливается, она сохраняет свое состояние в Continuation
, чтобы позже возобновить выполнение.
resumeWith(result: Result) - Возобновляет выполнение корутины с результатом или исключением.
suspend fun fetchData(): String {
return suspendCoroutine { continuation ->
// Имитация асинхронного запроса
Thread {
Thread.sleep(1000)
continuation.resumeWith(Result.success("Data fetched"))
}.start()
}
}
withContext — это функция, которая позволяет временно изменить контекст выполнения корутины.
Мы можем использовать функцию withContext
, чтобы изменить политику запуска задач на определённых потоках.
suspend fun fetchData(): String = withContext(Dispatchers.IO) {
delay(1000)
"Data fetched"
}
CoroutineContext — это контекст выполнения корутины, который содержит:
- Dispatcher - Определяет, на каком потоке выполняется корутина.
- Job - Управляет жизненным циклом корутины.
- CoroutineExceptionHandler - Обрабатывает исключения в корутине.
- Создание контекста:
val context = Dispatchers.IO + CoroutineName("MyCoroutine") + CoroutineExceptionHandler { _, exception ->
println("Caught exception: $exception")
}
- Запуск корутины с контекстом:
val job = CoroutineScope(context).launch {
println("Running in ${coroutineContext[CoroutineName]}")
throw IllegalStateException("Test exception")
}
resumeWith — это метод Continuation
, который возобновляет выполнение корутины с результатом или исключением.
suspend fun fetchData(): String {
return suspendCoroutine { continuation ->
// Имитация асинхронного запроса
Thread {
try {
val data = "Data fetched"
continuation.resumeWith(Result.success(data))
} catch (e: Exception) {
continuation.resumeWith(Result.failure(e))
}
}.start()
}
}
Dispatchers определяют, на каком потоке выполняется корутина. Основные диспетчеры:
- Dispatchers.Main - Основной поток (
UI
-поток вAndroid
). - Dispatchers.IO - Для
I/O
-операций (например, сетевые запросы, работа с файлами). - Dispatchers.Default - Для вычислительных задач.
- Dispatchers.Unconfined - Не привязывает корутину к конкретному потоку.
Sync (синхронный) — это выполнение кода в одном потоке, где каждая операция блокирует выполнение следующей до завершения текущей.
Async — это функция, которая запускает корутину и возвращает Deferred
, который можно использовать для получения результата.
fun main() = runBlocking {
val deferred = async {
delay(1000)
"Task completed"
}
println("Task started")
println(deferred.await())
}
Launch — это функция, которая запускает корутину без возврата результата. Она используется для выполнения фоновых задач.
Запустить корутину можно при помощи билдеров:
- launch – возвращает
Job
- async – возвращает
Deferred<T>
Обе функции являются экстеншенами к CoroutineScope
CoroutineScope — это область видимости для корутин. Она управляет жизненным циклом корутин и может быть отменена.
val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
delay(1000)
println("Task completed")
}
scope.cancel() // Отмена всех корутин в scope
CoroutineScope - Интерфейс с одним свойством. Обязательно:
- должен содержать в
coroutineContext
объект типаJob
- должен быть отменён, когда больше не нужен
Исходя из того, что CoroutineScope
нужно закрывать, логичнее всего привязать его к жизненному циклу компонентов.
MainScope — это предопределенный CoroutineScope
, который использует Dispatchers.Main
для выполнения корутин.
val mainScope = MainScope()
mainScope.launch {
delay(1000)
println("Task completed")
}
mainScope.cancel() // Отмена всех корутин в MainScope
Удобнее всего запускать корутины из главного потока.
Для этого нам потребуется создать CoroutineScope с Dispatchers.Main
.
Можем воспользоваться готовой функцией MainScope
.
Flow — это поток данных, который может emit
(излучать) несколько значений асинхронно.
Он похож на RxJava
Observable
, но более легковесный.
fun fetchData(): Flow<Int> = flow {
for (i in 1..3) {
delay(1000)
emit(i)
}
}
fun main() = runBlocking {
fetchData().collect { value -> println(value) }
}
Интерфейс с одной функцией. Смысл которой – подписаться.
FlowCollector — это интерфейс, который используется для сбора элементов из Flow
.
fun fetchData(): Flow<Int> = flow {
for (i in 1..3) {
delay(1000)
emit(i)
}
}
fun main() = runBlocking {
fetchData().collect { value -> println(value) }
}
В FlowCollector
можно передавать данные.
- Cold Flow - Начинает
emit
элементы только после подписки. Каждый подписчик получает свою копию данных. - Hot Flow -
Emit
элементы независимо от подписок. Подписчики получают только те элементы, которые былиemitted
после подписки.
По умолчанию Flow
, созданный через flow
билдер, является холодным.
Если нам нужна горячая версия, которая работает независимо от наличия подписчиков в одном экземпляре, то нам понадобится SharedFlow
или StateFlow
.
SharedFlow — это поток данных, который может иметь несколько подписчиков (hot flow
).
Он используется для передачи данных между корутинами.
val sharedFlow = MutableSharedFlow<Int>()
fun main() = runBlocking {
launch {
sharedFlow.collect { value -> println("Subscriber 1: $value") }
}
launch {
sharedFlow.collect { value -> println("Subscriber 2: $value") }
}
sharedFlow.emit(1)
sharedFlow.emit(2)
}
Наследуется от Flow
.
StateFlow — это поток данных, который хранит текущее состояние и уведомляет подписчиков об изменениях (hot flow
).
val stateFlow = MutableStateFlow(0)
fun main() = runBlocking {
launch {
stateFlow.collect { value -> println("State: $value") }
}
stateFlow.value = 1
stateFlow.value = 2
}
Является наследником SharedFlow
. Появляется возможность получить текущее значение.
/**
* Flow, хранящий текущее состояние пользователей.
*
* @see UserState Состояние, которое хранится в этом Flow.
*/
private val _state: MutableStateFlow<UserState> = MutableStateFlow(UserState())
/**
* Публичный Flow, который предоставляет доступ к текущему состоянию пользователей.
*
* @see UserState Состояние, которое предоставляется этим Flow.
*/
val state: StateFlow<UserState> = _state.asStateFlow()
MutableStateFlow — это изменяемая версия StateFlow
, которая позволяет обновлять текущее состояние.
val mutableStateFlow = MutableStateFlow(0)
fun main() = runBlocking {
launch {
mutableStateFlow.collect { value -> println("State: $value") }
}
mutableStateFlow.value = 1
mutableStateFlow.value = 2
}
Lifecycle Coroutines — это интеграция корутин с жизненным циклом Android
-компонентов (например, Activity
, Fragment
).
Это позволяет автоматически отменять корутины при уничтожении компонента.
Coroutines в ViewModel позволяют выполнять асинхронные операции, которые автоматически отменяются при очистке ViewModel
.
viewModel.state — это StateFlow
, который хранит текущее состояние (state
) в ViewModel
.
Обычно это неизменяемый поток данных, который обновляется в ViewModel
и может быть подписан в Fragment
или Activity
.
flowWithLifecycle — это функция из библиотеки androidx.lifecycle:lifecycle-runtime-ktx
, которая позволяет подписываться на Flow
только тогда, когда жизненный цикл Fragment
или Activity
находится в активном состоянии (например, STARTED
или RESUMED
).
Это предотвращает утечки памяти и ненужные обновления UI
, когда Fragment
не виден
.
- Если Fragment переходит в состояние
STARTED
илиRESUMED
, подписка наFlow
активируется. - Если Fragment переходит в состояние
STOPPED
илиDESTROYED
, подписка автоматически отменяется.
viewLifecycleOwner.lifecycle — это жизненный цикл Fragment
, который управляет подпиской.
.onEach - это оператор Flow
, который вызывается каждый раз, когда Flow
излучает новое значение.
Внутри этого блока можно обрабатывать изменения состояния (state
).
state — это текущее состояние, которое пришло из ViewModel
.
В зависимости от состояния (Loading
, Success
, Error
) обновляется UI
.
launchIn — это функция, которая запускает Flow
в указанной CoroutineScope
.
В данном случае используется lifecycleScope
Fragment
, который автоматически отменяет корутину, когда Fragment
уничтожается.
Как это работает?
launchIn
запускаетFlow
в фоновом потоке (если не указан другойDispatcher
).- Подписка на
Flow
будет активна до тех пор, пока Fragment находится в активном состоянии. - Когда
Fragment
уничтожается, подписка автоматически отменяется, предотвращая утечки памяти.
Здесь viewLifecycleOwner.lifecycleScope
— это CoroutineScope
, привязанный к жизненному циклу Fragment
.
viewModel.state
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.onEach { state ->
// Работа с потоком состояния StateFlow в View Model
}
.launchIn(viewLifecycleOwner.lifecycleScope)
- Coroutines: Легковесные потоки для асинхронного программирования.
- Suspend: Функции, которые могут быть приостановлены и возобновлены.
- Continuation: Объект, представляющий точку возобновления корутины.
- withContext: Временное изменение контекста выполнения.
- CoroutineContext: Контекст выполнения корутины, включающий Dispatcher, Job и ExceptionHandler.
- resumeWith: Метод для возобновления корутины с результатом или исключением.
- Dispatchers: Управление потоками выполнения.
- Launch/Async: Запуск корутин.
- CoroutineScope/MainScope: Области видимости для корутин.
- Flow: Поток данных, который может emit несколько значений.
- FlowCollector: Сбор элементов из Flow.
- Hot/Cold Flow: Разные типы потоков данных.
- SharedFlow/StateFlow: Потоки данных с поддержкой нескольких подписчиков.
- MutableStateFlow: Изменяемая версия StateFlow.
- Lifecycle Coroutines: Интеграция корутин с жизненным циклом Android-компонентов.
- Coroutines в ViewModel: Выполнение асинхронных операций в ViewModel с автоматической отменой.
- Retrofit + Coroutines: Интеграция сетевых запросов с корутинами.