Avatar ·

Что использовать вместо устаревшего Handler() в Kotlin/Android?

Всем привет! Я новичок в Android-разработке на Kotlin, и столкнулся с проблемой. Раньше я использовал `Handler()` для выполнения задач с задержкой или переключения на главный поток из фонового. Например, у меня был такой код: ```kotlin val handler = Handler() handler.postDelayed({ // какой-то код через 2 секунды }, 2000) ``` Но недавно я обновил SDK, и теперь IDE подчёркивает `Handler()` красным — пишет, что конструктор устарел (deprecated). В документации сказано использовать `Looper.getMainLooper()`, но я не до конца понимаю, как это правильно сделать. Также я слышал про `HandlerThread` и `Coroutines`, но не знаю, что лучше для простых задач вроде задержки перед обновлением UI. Можете объяснить, как теперь правильно писать код, который раньше делался через `Handler()`? Желательно с примерами на Kotlin, чтобы было понятно новичку. Заранее спасибо! <<>>

Почему Handler() устарел и что теперь использовать?

В Android 11 (API 30) конструктор Handler() без явного указания Looper был помечен как deprecated. Это сделано для повышения надёжности: раньше вы неявно привязывались к Looper текущего потока, который мог оказаться фоновым, что приводило к ошибкам. Теперь нужно всегда указывать Looper явно. Вот правильные подходы.

1. Классический Handler с явным Looper

Самый простой способ — передать Looper.getMainLooper() для работы с UI:

val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
    // Этот код выполнится на главном потоке через 2 секунды
    textView.text = "Прошло 2 секунды"
}, 2000)

Если вам нужно выполнить задачу в фоновом потоке, используйте HandlerThread:

val handlerThread = HandlerThread("BackgroundThread")
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)

backgroundHandler.post {
    // Тяжёлая работа (не на главном потоке)
    val result = doLongTask()
    
    // Переключаемся на главный для обновления UI
    Handler(Looper.getMainLooper()).post {
        textView.text = result
    }
}

2. Современный подход: Kotlin Coroutines (рекомендуется)

Для новых проектов Google рекомендует использовать корутины. Они лаконичнее и безопаснее. Подключите зависимость в build.gradle:

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")

Теперь задержка и переключение потоков выглядят так:

// В Activity или ViewModel
lifecycleScope.launch {
    delay(2000) // Неблокирующая задержка
    textView.text = "Прошло 2 секунды (корутина)"
}

// Для фоновой задачи
lifecycleScope.launch(Dispatchers.IO) {
    val result = doLongTask() // Выполняется в фоне
    withContext(Dispatchers.Main) {
        textView.text = result // Возвращаемся на главный
    }
}

3. View.postDelayed (для простых UI-задержек)

Если вам нужно просто отложить выполнение кода, связанного с конкретным View, можно использовать метод самого View:

textView.postDelayed({
    textView.text = "Прошло 2 секунды"
}, 2000)

Этот метод не устарел и автоматически работает на главном потоке.

Рекомендации

  • Для простых задержек в UI — используйте View.postDelayed() или Handler(Looper.getMainLooper()).
  • Для сложной асинхронности (сетевые запросы, БД, многопоточность) — переходите на корутины. Это современный стандарт Android-разработки.
  • Не используйте пустой Handler() — это источник багов.

Пример полного класса с корутинами:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        lifecycleScope.launch {
            delay(1000)
            findViewById<TextView>(R.id.textView).text = "Привет из корутины"
        }
    }
}

Корутины легко отменяются при уничтожении Activity (через lifecycleScope), что предотвращает утечки памяти.

<<>>

Альтернативный ответ: Использование HandlerThread для фоновых задач

Если вы не хотите менять архитектуру на корутины, но нужно выполнять задачи в фоне с возможностью отмены, можно использовать HandlerThread в сочетании с Handler. Вот простой пример:

class MyBackgroundTask {
    private val thread = HandlerThread("WorkerThread").apply { start() }
    private val handler = Handler(thread.looper)
    
    fun doDelayedWork(delayMs: Long, action: () -> Unit) {
        handler.postDelayed(action, delayMs)
    }
    
    fun cancelAll() {
        handler.removeCallbacksAndMessages(null)
    }
    
    fun cleanup() {
        thread.quitSafely()
    }
}

Использование:

val task = MyBackgroundTask()
task.doDelayedWork(2000) {
    // Выполнится через 2 секунды в фоновом потоке
    Log.d("TAG", "Фоновая работа")
    // Для UI используйте Handler(Looper.getMainLooper())
}

// При уничтожении Activity
override fun onDestroy() {
    task.cleanup()
    super.onDestroy()
}

Этот подход хорош, когда нужно управлять очередью задач вручную, но он требует явного освобождения ресурсов. Для большинства современных проектов корутины удобнее.