Почему вам это нужно прямо сейчас
Представьте: вы пишете приложение, и вам нужно показать пользователю уведомление через 5 секунд после нажатия кнопки. Или выполнить запрос к серверу только через 2 секунды после того, как пользователь закончил вводить текст. Или просто сделать красивую анимацию с задержкой. Без умения вызывать функцию с задержкой в Kotlin — никуда.
Этот вопрос — один из самых популярных на Stack Overflow (тысячи просмотров!), и не зря. В Kotlin есть целых 4 способа сделать отложенный вызов, и каждый подходит для своей ситуации. В этой статье мы разберём их все: от простого delay() в корутинах до классического Handler для Android. Вы узнаете, какой метод выбрать и почему, а главное — увидите рабочий код, который можно сразу скопировать в свой проект.
Способ 1: Корутины и delay() (современный и предпочтительный)
Это самый элегантный и рекомендуемый способ в современном Kotlin. Корутины — это лёгкие потоки, которые не блокируют основной поток. Метод delay() приостанавливает корутину на указанное время, а затем возобновляет её.
Для работы с корутинами нужно добавить зависимость в build.gradle.kts:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
А вот как это выглядит в коде:
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Старт: ${System.currentTimeMillis()}")
// Запускаем корутину с задержкой
launch {
delay(3000L) // ждём 3 секунды
println("Прошло 3 секунды: ${System.currentTimeMillis()}")
myDelayedFunction() // вызываем нужную функцию
}
println("Ждём... (это выполнится сразу)")
}
fun myDelayedFunction() {
println("Функция выполнена!")
}
Важно: delay() — это приостанавливающая функция (suspend function). Её можно вызывать только внутри корутины или другой suspend-функции. Если вы попробуете вызвать delay() вне корутины, компилятор выдаст ошибку.
Как сделать отложенный вызов в Android с корутинами
В Android-разработке корутины — стандарт де-факто. Вот пример для Activity:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Через 2 секунды показать тост
scope.launch {
delay(2000L)
showToast("Прошло 2 секунды!")
}
}
private fun showToast(message: String) {
android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_SHORT).show()
}
override fun onDestroy() {
super.onDestroy()
scope.cancel() // отменяем все задачи при уничтожении Activity
}
}
Обратите внимание: мы используем Dispatchers.Main, чтобы работать с UI. И обязательно отменяем scope в onDestroy(), чтобы избежать утечек памяти.
Способ 2: Handler и postDelayed() (классика Android)
Если вы пишете под Android и не хотите (или не можете) использовать корутины, старый добрый Handler всё ещё работает. Этот метод был популярен до эры корутин.
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Вызов функции через 5 секунд
handler.postDelayed({
showToast("Handler сработал через 5 секунд!")
}, 5000L)
}
private fun showToast(message: String) {
android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_SHORT).show()
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null) // очищаем все отложенные задачи
}
}
Плюсы: простота, работает на старых версиях Android.
Минусы: легко забыть отменить задачу и получить утечку памяти. Код становится громоздким при большом количестве задержек.
Способ 3: Timer и TimerTask (для Java-разработчиков)
Этот способ пришёл из Java. Он тоже работает, но считается устаревшим для Android (может вызвать проблемы с жизненным циклом).
import java.util.*
fun main() {
println("Старт: ${System.currentTimeMillis()}")
Timer().schedule(object : TimerTask() {
override fun run() {
println("Timer сработал через 4 секунды: ${System.currentTimeMillis()}")
myDelayedFunction()
}
}, 4000L)
println("Программа продолжает работать...")
Thread.sleep(5000L) // чтобы программа не завершилась сразу
}
fun myDelayedFunction() {
println("Функция из Timer выполнена!")
}
Внимание: Timer выполняется в фоновом потоке. Если вы работаете с UI в Android, нужно переключиться на главный поток с помощью runOnUiThread.
Способ 4: Thread.sleep() (почти никогда не используйте)
Технически, вы можете просто усыпить поток на нужное время, а потом вызвать функцию. Но это плохая практика, особенно в Android:
fun main() {
println("Старт")
Thread.sleep(3000L) // блокирует весь поток на 3 секунды!
println("Прошло 3 секунды")
myDelayedFunction()
}
Почему это плохо:
- Блокирует поток. В Android это приведёт к зависанию UI (ANR — Application Not Responding).
- Никакой гибкости: нельзя отменить задачу, нельзя запустить несколько параллельных задержек.
- Используйте sleep() только в учебных примерах или в фоновых потоках, где это абсолютно необходимо.
Сравнение всех способов
Чтобы вам было проще выбрать, вот сводная таблица:
- Coroutines + delay() — современно, не блокирует поток, легко отменять. Лучший выбор для новых проектов.
- Handler + postDelayed() — классика Android, подходит для простых задач. Требует ручного управления отменой.
- Timer + TimerTask — устаревший, не рекомендован для Android. Работает в фоновом потоке.
- Thread.sleep() — блокирует поток. Избегайте в продакшене.
Как отменить отложенный вызов
Иногда нужно отменить запланированную задачу (например, пользователь ушёл