Handler() устарел в Android? Что использовать в 2025

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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())
  • От

Блоги

Рекомендаций по книгам