Может ли Handler работать без Looper и что происходит если callback выполняется на потоке без Looper
Что проверяют
Понимаете ли вы связку Handler, Looper, MessageQueue и то, что Handler привязан к лупперу, а не к потоку, где его создали. Следом обычно просят объяснить, как устроен HandlerThread и почему конструктор без аргументов объявлен устаревшим.
Как отвечать
Без Looper Handler работать не может. Разберите три ситуации:
- создание на потоке без Looper:
Handler()без аргументов берётLooper.myLooper(), получает null и бросаетRuntimeExceptionс текстом про то, что поток не вызвалLooper.prepare(). Поэтому с Android 11 (API 30) этот конструктор deprecated, лупер передают явно:Handler(Looper.getMainLooper()); - Looper подготовлен, но не запущен:
Looper.prepare()вызван, аLooper.loop()нет. Сообщения молча копятся в очереди и не выполняются никогда; - Looper остановлен: после
quit()илиquitSafely()вызовsendMessage()не бросает исключение, а возвращаетfalseи пишет в лог предупреждение про dead thread. Задача просто теряется.
Теперь про колбэк. То, что отправлено через Handler, всегда исполняется на потоке его лупера, откуда бы ни вызвали post(). Поэтому колбэк самого Handler на потоке без лупера невозможен по построению. Проблема возникает с кодом, который крутится на обычном потоке из Executor или Thread и дёргает API, ожидающие лупер: Toast.makeText() падает с похожим текстом про Looper.prepare(), LiveData.setValue() бросает IllegalStateException, изменение view даёт CalledFromWrongThreadException.
Правильный путь: HandlerThread, у которого лупер уже поднят, postValue() вместо setValue(), а в современном коде корутины с Dispatchers.Main, который сам построен на Handler главного потока.
Где спотыкаются
Думают, что Handler выполняет код на потоке, где его создали. Решает только переданный Looper.
Забывают Looper.loop() в самописном потоке и потом ищут, куда пропали сообщения.
Не останавливают HandlerThread через quitSafely(). Поток живёт вечно, а отложенные сообщения держат ссылки на Activity и дают утечку.