Handler() устарел: что теперь? Взгляд в 2025 год
Если вы хоть раз писали под Android, то наверняка использовали Handler() для переключения между потоками, отложенных задач или обновления UI. Этот старый добрый класс был с нами с самых первых версий Android. Но в 2024-2025 годах Google официально объявил конструктор Handler() deprecated (устаревшим). Тысячи разработчиков на Stack Overflow задаются вопросом: «What do I use now that Handler() is deprecated?»
В этой статье мы разберёмся, почему Handler() устарел, какие альтернативы предлагает Android в 2025 году, и самое главное — как писать современный, чистый код на Kotlin, который не вызовет слёз у вашего тимлида. Поехали!
Почему Handler() стал deprecated?
Класс Handler сам по себе никуда не делся. Устарел именно конструктор без аргументов (Handler()) и его перегрузки, которые неявно создавали Looper из текущего потока. Проблема в том, что такое поведение:
- Неявно и небезопасно — если вызвать
Handler()в фоновом потоке, у которого нетLooper, вы получитеRuntimeException. - Усложняет тестирование —
Handlerтесно связан с жизненным циклом потоков, что делает юнит-тесты головной болью. - Устарел морально — появились более современные и декларативные инструменты: корутины,
HandlerThread(в явном виде) иLiveData.
Google рекомендует явно передавать Looper в конструктор, либо использовать альтернативы. Давайте посмотрим, что именно пришло на смену.
Альтернатива №1: Явный Looper (самый простой фикс)
Если вы не хотите переписывать весь код, самый быстрый способ убрать deprecated — передать Looper явно. Для главного потока (UI thread) используйте Looper.getMainLooper().
// Старый код (deprecated)
val handler = Handler()
// Новый код (2025)
val handler = Handler(Looper.getMainLooper())
// Или для фонового потока:
val handler = Handler(Looper.myLooper()) // Внимание: может быть null!
Это не решит всех проблем, но уберёт предупреждение компилятора. Однако для серьёзных проектов лучше перейти на корутины.
Альтернатива №2: Kotlin Coroutines (рекомендованный Google)
С 2021 года Google официально рекомендует Kotlin Coroutines для асинхронных операций. В 2025 году это уже стандарт индустрии. Почему корутины лучше Handler?
- Легковесность — тысячи корутин не блокируют потоки.
- Структурированная конкурентность — автоматическая отмена при завершении жизненного цикла.
- Читаемость — код выглядит как синхронный, без callback-ов.
- Тестируемость —
runBlockingиTestCoroutineDispatcherдля юнит-тестов.
Вот как заменить типичные задачи Handler на корутины:
Замена postDelayed (отложенная задача)
// Старый способ с Handler
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
// выполнить через 2 секунды
}, 2000)
// Новый способ с Coroutines
lifecycleScope.launch {
delay(2000) // не блокирует поток!
// выполнить через 2 секунды
}
Замена post (переключение на UI поток)
// Старый способ
handler.post {
textView.text = "Привет из фонового потока"
}
// Новый способ
lifecycleScope.launch(Dispatchers.Main) {
textView.text = "Привет из корутины"
}
Замена removeCallbacks (отмена задачи)
// Старый способ
val runnable = Runnable { /*...*/ }
handler.postDelayed(runnable, 1000)
handler.removeCallbacks(runnable)
// Новый способ
val job = lifecycleScope.launch {
delay(1000)
// ...
}
job.cancel() // отменяем задачу
Важно: Используйте lifecycleScope (в Activity/Fragment) или viewModelScope (в ViewModel), чтобы корутина автоматически отменялась при уничтожении компонента.
Альтернатива №3: HandlerThread (для фоновых очередей)
Если вам нужен выделенный фоновый поток с собственной очередью сообщений (как старый добрый HandlerThread), используйте его с явным Looper. Ничего устаревшего в этом нет.
val handlerThread = HandlerThread("BackgroundThread")
handlerThread.start()
// Создаём Handler для этого потока
val backgroundHandler = Handler(handlerThread.looper)
backgroundHandler.post {
// Тяжёлая работа в фоне
val result = doHeavyWork()
// Переключаемся на UI поток через корутину
lifecycleScope.launch(Dispatchers.Main) {
textView.text = result
}
}
// Не забывайте завершить поток
override fun onDestroy() {
handlerThread.quitSafely()
super.onDestroy()
}
Этот подход всё ещё актуален для работы с сокетами, Bluetooth, или когда вам нужна последовательная обработка сообщений без корутин.
Альтернатива №4: LiveData и StateFlow (для реактивного UI)
Если вы использовали Handler для обновления UI после фоновой работы, задумайтесь: может быть, вам нужна реактивная архитектура?
LiveData— простой и понятный, идеален для Activity/Fragment.StateFlow— более мощный, поддерживает корутины и работает с Compose.
// ViewModel с StateFlow
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow("Загрузка...")
val uiState: StateFlow<String> = _uiState.asStateFlow()
fun loadData() {
viewModelScope.launch {
val data = fetchFromNetwork()
_uiState.value = data
}
}
}
// В Activity
lifecycleScope.launch {
viewModel.uiState.collect { text ->
textView.text = text
}
}
Такой код чище, безопаснее и легче тестируется, чем связка Handler + Runnable.
Что выбрать в 2025? Сравнительная таблица
Чтобы вам было проще ориентироваться, вот краткая шпаргалка:
- Просто убрать предупреждение →
Handler(Looper.getMainLooper()) - От