Что такое handler java
Класс Handler
Класс android.os.Handler является дальнейшим развитием потоков, упрощающий код. Handler может использоваться для планирования выполнения кода в некоторый момент в будущем. Также класс может использоваться для передачи кода, который должен выполняться в другом программном потоке.
Рассмотрим максимально простой пример для знакомства
Запустите пример на эмуляторе и через некоторое время закройте его через кнопку Назад или Домой. При этом смотрите на логи на вкладе Android Monitor. Вы увидите, что приложение по-прежнему печатает текст типа после секундной задержки после запуска.
Разберёмся, что происходит и как следует читать код.
Блок if позволяет управлять кодом для потока. Сам запуск мы сделаем позже. А в самом блоке мы снова получаем текущее время и сравниваем с самой первым временем, полученным во время запуска. Для удобства вычисления идут в секундах. Результат выводится в лог. Метод sendEmptyMessageDelayed() сообщает системе, что мы хотим повторять код в handleMessage() раз в секунду.
После инициализации и настройки mHandler мы присваиваем значение true переменной gameOn, показывая готовность к запуску кода из блока if.
Последняя строка sendEmptyMessage() запускает поток.
Можно использовать более сложные приёмы запуска потока, но пока этого достаточно для понимания.
Периодическое выполнение задачи
При сложных вычислениях может понадобиться очередь Runnable-объектов. Помещая объект в очередь, вы можете задать время его запуска. Для демонстрации использования обработчика потока напишем программу, запускающую фоновый процесс, который будет каждые 200 миллисекунд получать текущее время и обновлять текст. Нам понадобится кнопка Пуск и две текстовые метки, в которых будет отображаться время и количество нажатий кнопки:
На экране будет отображаться время и одновременно мы можем нажимать на кнопку. Эти действия не мешают друг другу, так как работают в разных потоках.
Кроме метода postDelayed() вы можете использовать метод postAtTime():
В этом случае объект r добавляется в очередь сообщений, запуск объекта производится во время, заданное вторым параметром.
Пример с индикатором прогресса
Чтобы послать сообщение в объект Handler, сначала необходимо вызвать метод obtainMessage(), чтобы извлечь объект Message из глобального пула сообщений.
Для вставки сообщения в очередь сообщений объекта Handler существует несколько методов:
Чтобы обрабатывать эти сообщения, для объекта Handler необходимо реализовать метод обратного вызова handleMessage(), который будет вызываться каждым сообщением из очереди сообщения.
Для примера создадим приложение с ProgressBar, который будет отображать ход выполнения длительной задачи (это будет простой цикл с приостановкой потока на 1 секунду в каждой итерации цикла) и обновлять степень завершения этой задачи через объект Handler в классе активности.
Splash-screen
Очень часто программисты используют Handler для реализации окна приветствия, которое автоматически закрывается и следом запускается основная активность игры или приложения.
Полный список
— посылаем простейшее сообщение для Handler
Надеюсь, вы прониклись предыдущим уроком и осознали, какая полезная штука Handler. Мы там отправляли ему сообщение. Сегодня сделаем это еще раз, но уже без кучи лишнего кода, зависающих экранов и ошибок приложения. Этакий чистый пример, чтобы закрепить.
Как мы помним, Handler позволяет класть в очередь сообщения и сам же умеет их обрабатывать. Фишка тут в том, что положить сообщение он может из одного потока, а прочесть из другого.
Сообщение может содержать в себе атрибуты. Сегодня рассмотрим самый простой вариант, атрибут what.
Напишем простое приложение-клиент. Оно, как-будто, будет подключаться к серверу, выполнять какую-то работу и отключаться. На экране мы будем наблюдать, как меняется статус подключения и как крутится ProgressBar при подключении.
При сменах состояния подключения мы будем отправлять сообщение для Handler. А в атрибут what будем класть текущий статус. Handler при обработке сообщения прочтет из него what и выполнит какие-либо действия.
Project name: P0811_ HandlerSimpleMessage
Build Target: Android 2.3.3
Application name: HandlerSimpleMessage
Package name: ru.startandroid.develop.p0811handlersimplemessage
Create Activity: MainActivity
Кнопка для старта подключения, TextView для вывода информации о статусе подключения и ProgressBar, работающий в процессе подключения.
STATUS_NONE, STATUS_CONNECTING, STATUS_CONNECTED – это константы статуса. Их будем передавать в сообщении, в атрибуте what. Разумеется, названия и значения этих констант произвольны и взяты из головы. Вы можете придумать и использовать свои.
В onCreate мы создаем Handler и реализуем его метод handleMessage. Этот метод отвечает за обработку сообщений, которые предназначены для этого Handler. Соответственно на вход метода идет сообщение – Message. Мы читаем атрибут what и в зависимости от статуса подключения меняем экран:
STATUS_NONE – нет подключения. Кнопка подключения активна, TextView отражает статус подключения.
STATUS_CONNECTING – в процессе подключения. Кнопка подключения неактивна, показываем ProgressBar, TextView отражает статус подключения.
STATUS_CONNECTED – подключено. Скрываем ProgressBar, TextView отражает статус подключения.
В onCreate после создания Handler мы сразу отправляем ему сообщение со статусом STATUS_NONE. Для этого мы используем метод sendEmptyMessage. В этом методе создается сообщение, заполняется его атрибут what (значением, которое мы передаем в sendEmptyMessage), устанавливается Handler в качестве адресата и сообщение отправляется в очередь.
В методе onclick мы создаем и запускаем новый поток. В нем мы, с помощью sleep, эмулируем процесс подключения к серверу, выполнение работы и отключение. И, по мере выполнения действий, отправляем сообщения со статусами для Handler. Т.е. получается, что после нажатия на кнопку Connect статус меняется на STATUS_CONNECTING, две секунды идет подключение, статус меняется на STATUS_CONNECTED, 3 секунды выполняются действия и статус меняется на STATUS_NONE. Давайте проверим.
Все сохраним и запустим приложение.
Подключение пошло. Появляется ProgressBar, меняется текст и неактивна кнопка Connect.
Проходит две секунды.
Подключение установлено. ProgressBar исчезает и меняется текст.
Проходит еще 3 секунды.
Подключение завершается. Кнопка Connect снова активна, а текст показывает, что мы не подключены.
Т.е. для простого обновления статуса из нового потока нам хватило атрибута what. Но кроме what сообщение может иметь еще несколько атрибутов. Рассмотрим их на следующем уроке.
На следующем уроке:
— создаем более содержательные сообщения для Handler
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Полный список
— посылаем отложенные сообщения
— удаляем сообщения из очереди
— используем Handler.Callback для обработки сообщений
В прошлых уроках мы отправляли сообщения в очередь, а система сразу же доставала их и перенаправляла в Handler на обработку. Но мы можем настроить сообщение так, чтобы система отправило его на обработку не сразу, а с задержкой. Для этого используются методы sendEmptyMessageDelayed (если используете только what) и sendMessageDelayed (полное сообщение). В них мы можем указать паузу в миллисекундах. Система выждет эту паузу и только потом отправит сообщение в Handler.
Если вдруг поместили такое отложенное сообщение в очередь, а потом решили, что оно не должно уйти на обработку, то его можно из очереди удалить. Для этого используется метод removeMessages.
В прошлых уроках мы создавали свой Handler, и в его методе handleMessage кодили свой алгоритм обработки сообщений. Кроме этого способа Handler также может использовать для обработки сообщений объект, реализующий интерфейс Handler.Callback. У интерфейса всего один метод handleMessage – в нем и прописываем всю логику обработки сообщений. Я пока не встречал практической пользы от этой штуки, но все же разберемся, как ее можно использовать. Может когда и пригодится.
Project name: P0831_HandlerMessageManage
Build Target: Android 2.3.3
Application name: HandlerMessageManage
Package name: ru.startandroid.develop.p0831handlermessagemanage
Create Activity: MainActivity
strings.xml и main.xml не трогаем, они нам не нужны. Будем работать с логами.
Мы создаем объект hc типа Handler.Callback. У него есть метод handleMessage, в котором мы будем обрабатывать сообщения. В нашем случае просто читаем атрибут what и выводим значение в лог.
Итак, мы помещаем три сообщения:
1) what = 1, обработка через 1000 мс.
2) what = 2, обработка через 2000 мс.
3) what = 3, обработка через 3000 мс.
Замечу, что отсчет задержки начинается после помещения в очередь, а не после обработки предыдущего сообщения. Т.е. эти сообщения по отношению друг к другу сработают с интервалом в одну секунду.
Все сохраним и запустим приложение. В логе одна за другой будут появляться записи:
10:21:07.759: D/myLogs(332): send messages
10:21:08.786: D/myLogs(332): what = 1
10:21:09.765: D/myLogs(332): what = 2
10:21:10.776: D/myLogs(332): what = 3
Теперь попробуем удалить сообщение из очереди. Перепишем метод sendMessages:
Используем метод removeMessages, в котором указываем значение атрибута what. Этот метод находит в очереди сообщение с атрибутом what, равным 2, и удаляет его из очереди.
Все сохраняем, запускаем приложение. Смотрим лог:
10:24:49.916: D/myLogs(434): send messages
10:24:50.927: D/myLogs(434): what = 1
10:24:52.948: D/myLogs(434): what = 3
Как видим, сообщение с what = 2 не сработало.
А если будет несколько сообщений с одинаковым значением what? Система удалит первое попавшееся или все?
Проверим. Перепишем sendMessages:
Будем помещать в очередь кучу сообщений. Из них несколько с what = 2. Проверим, какие удалит система.
Запускаем приложение и смотрим лог:
10:29:23.297: D/myLogs(467): send messages
10:29:24.372: D/myLogs(467): what = 1
10:29:26.307: D/myLogs(467): what = 3
10:29:28.364: D/myLogs(467): what = 5
10:29:30.332: D/myLogs(467): what = 7
Все сообщения с what = 2 были удалены. Не забывайте это. А то захотите удалить одно последнее сообщение, а система найдет все подходящие, ожидающие обработки, и снесет их.
У метода removeMessages есть еще реализация с использованием obj. Тут все так же, только система ищет для удаления из очереди сообщения с указанными атрибутами what и obj.
Если хотите запланировать полноценное сообщение, а не просто what, то используйте метод sendMessageDelayed – на вход даете сообщение и указываете задержку обработки.
Есть еще методы sendEmptyMessageAtTime и sendMessageAtTime. Они тоже позволяют указать задержку обработки. Но эта задержка будет отсчитана от времени последнего старта системы, а не от времени помещения в очередь. Если сообщение окажется просроченным на момент помещения в очередь, оно выполняется сразу.
На следующем уроке:
— работаем с Handler и Runnable
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Полный список
— создаем более содержательные сообщения для Handler
В прошлых уроках мы использовали метод sendEmptyMessage. Этот метод сам создавал сообщение Message, заполнял его атрибут what и отправлял в очередь. Кроме what у сообщения есть еще атрибуты arg1 и arg2 типа int, и obj типа Object. В этом уроке мы сами будем создавать сообщение, заполнять атрибуты и отправлять.
Создадим приложение, которое будет подключаться к серверу, запрашивать кол-во файлов готовых для загрузки, эмулировать загрузку и отображать на экране ход действий, используя горизонтальный ProgressBar и TextView.
Project name: P0821_HandlerAdvMessage
Build Target: Android 2.3.3
Application name: HandlerAdvMessage
Package name: ru.startandroid.develop.p0821handleradvmessage
Create Activity: MainActivity
В onCreate мы создаем Handler и в его методе обработки (handleMessage) прописываем всю логику изменения экрана в зависимости от приходящих сообщений. Не буду подробно это расписывать, там все просто – меняем текст, включаем/выключаем кнопку, показываем/скрываем ProgressBar, меняем значение ProgressBar. Из интересного здесь стоит отметить, что читаем мы на этот раз не только what, но и остальные атрибуты сообщения – arg1, arg2, obj. А как они заполняются, увидим далее.
Далее начинаем загрузку. После загрузки каждого файла мы создаем сообщение Message c помощью метода obtainMessage (int what, int arg1, int arg2, Object obj), заполняем его атрибуты: what – статус, arg1 – порядковый номер файла, arg2 – кол-во оставшихся файлов, obj – файл. И отправляем.
По завершению загрузки отправляем соответствующее сообщение и отключаемся.
downloadFile – эмулирует загрузку файла. ждет две секунды и возвращает массив из 1024 байтов.
saveFile – метод сохранения файла на диск. Просто заглушка. Ничего не делает.
Все сохраняем и запускаем. Жмем Connect.
Далее, либо начинается загрузка
либо появляется сообщение, что файлов нет
Используя разные атрибуты кроме what, мы смогли передать в основной поток и использовать там более разнообразные данные.
Мы создаем сообщения с помощью разных реализаций метода obtainMessage. А почему бы не создавать напрямую объект Message с помощью его конструкторов? В принципе можно, но официальный хелп рекомендует пользоваться методами obtainMessage, потому что это эффективней и быстрее. В этом случае сообщение достается из глобального пула сообщений, а не создается с нуля.
Здесь вы можете посмотреть все реализации метода obtainMessage для формирования сообщений и использовать тот, который подходит для ситуации. Они различаются разными комбинациями входных параметров.
На следующем уроке:
— посылаем отложенные сообщения
— удаляем сообщения из очереди
— используем Handler.Callback для обработки сообщений
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Урок 80. Handler. Немного теории. Наглядный пример использования
— разбираемся, что такое Handler и зачем он нужен
Для полного понимания урока желательно иметь представление о потоках (threads) в Java.
Так просто ведь и не объяснишь, что такое Handler. Можете попробовать почитать официальное описание, но там достаточно нетривиально и мало написано. Я попробую здесь в двух словах рассказать.
В Android к потоку (thread) может быть привязана очередь сообщений. Мы можем помещать туда сообщения, а система будет за очередью следить и отправлять сообщения на обработку. При этом мы можем указать, чтобы сообщение ушло на обработку не сразу, а спустя определенное кол-во времени.
Handler дает нам две интересные и полезные возможности:
1) реализовать отложенное по времени выполнение кода
2) выполнение кода не в своем потоке
В этом уроке сделаем небольшое приложение. Оно будет эмулировать какое-либо долгое действие, например закачку файлов и в TextView выводить кол-во закачанных файлов. С помощью этого примера мы увидим, зачем может быть нужен Handler.
Project name: P0801_Handler
Build Target: Android 2.3.3
Application name: Handler
Package name: ru.startandroid.develop.p0801handler
Create Activity: MainActivity
ProgressBar у нас будет крутиться всегда. Позже станет понятно, зачем. TextView – для вывода информации о закачке файлов. Кнопка Start будет стартовать закачку. Кнопка Test будет просто выводить в лог слово test.
В обработчике кнопки Start мы организуем цикл для закачки файлов. В каждой итерации цикла выполняем метод downloadFile (который эмулирует закачку файла), обновляем TextView и пишем в лог информацию о том, что кол-во закачанных файлов изменилось. Итого у нас должны закачаться 10 файлов и после закачки каждого из них лог и экран должны показывать, сколько файлов уже закачано.
По нажатию кнопки Test – просто выводим в лог сообщение.
downloadFile – эмулирует закачку файла, это просто пауза в одну секунду.
Все сохраним и запустим приложение.
Мы видим, что ProgressBar крутится. Понажимаем на кнопку Test, в логах появляется test. Все в порядке, приложение отзывается на наши действия.
Теперь расположите AVD на экране монитора так, чтобы он не перекрывал вкладку логов в Eclipse (LogCat). Нам надо будет видеть их одновременно.
Если мы нажмем кнопку Start, то мы должны наблюдать, как обновляется TextView и пишется лог после закачки очередного файла. Но на деле будет немного не так. Наше приложение просто «зависнет» и перестанет реагировать на нажатия. Остановится ProgressBar, не будет обновляться TextView, и не будет нажиматься кнопка Test. Т.е. UI (экран) для нас станет недоступным. И только по логам будет понятно, что приложение на самом деле работает и файлы закачиваются. Нажмите Start и убедитесь.
Экран «висит», а логи идут. Как только все 10 файлов будут закачаны, приложение оживет и снова станет реагировать на ваши нажатия.
А все почему? Потому что работа экрана обеспечивается основным потоком приложения. А мы заняли весь этот основной поток под свои нужды. В нашем случае, как будто под закачку файлов. И как только мы закончили закачивать файлы – поток освободился, и экран стал снова обновляться и реагировать на нажатия.
Т.е. мы просто помещаем весь цикл в новый поток и запускаем его. Теперь закачка файлов пойдет в этом новом потоке. А основной поток будет не занят и сможет без проблем прорисовывать экран и реагировать на нажатия. А значит, мы будем видеть изменение TextView после каждого закачанного файла и крутящийся ProgressBar. И, вообще, сможем полноценно взаимодействовать с приложением. Казалось бы, вот оно счастье 🙂
Все сохраним и запустим приложение. Жмем Start.
Приложение вылетело с ошибкой. Смотрим лог ошибок в LogCat. Там есть строки:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Смотрим, что за код у нас в MainActivity.java в 37-й строке:
При попытке выполнить этот код (не в основном потоке) мы получили ошибку «Only the original thread that created a view hierarchy can touch its views». Если по-русски, то «Только оригинальный поток, создавший view-компоненты, может взаимодействовать с ними». Т.е. работа с view-компонентами доступна только из основного потока. А новые потоки, которые мы создаем, не имеют доступа к элементам экрана.
Т.е. с одной стороны нельзя загружать основной поток тяжелыми задачами, чтобы не «вешался» экран. С другой стороны – новые потоки, созданные для выполнения тяжелых задач, не имеют доступа к экрану, и мы не сможем из них показать пользователю, что наша тяжелая задача как-то движется.
Тут нам поможет Handler. План такой:
— мы создаем в основном потоке Handler
— в потоке закачки файлов обращаемся к Handler и с его помощью помещаем в очередь сообщение для него же самого
— система берет это сообщение, видит, что адресат – Handler, и отправляет сообщение на обработку в Handler
— Handler, получив сообщение, обновит TextView
Чем это отличается от нашей предыдущей попытки обновить TextView из другого потока? Тем, что Handler был создан в основном потоке, и обрабатывать поступающие ему сообщения он будет в основном потоке, а значит, будет иметь доступ к экранным компонентам и сможет поменять текст в TextView. Получить доступ к Handler из какого-либо другого потока мы сможем без проблем, т.к. основной поток монополизирует только доступ к UI. А элементы классов (в нашем случае это Handler в MainActivity.java) доступны в любых потоках. Таким образом Handler выступит в качестве «моста» между потоками.
Перепишем метод onCreate:
Метод onclick перепишем так:
Мы деактивируем кнопку Start перед запуском закачки файлов. Это просто защита, чтобы нельзя было запустить несколько закачек одновременно. А в процессе закачки, после каждого закачанного файла, отправляем (sendEmptyMessage) для Handler сообщение с кол-вом уже закачанных файлов. Handler это сообщение примет, извлечет из него кол-во файлов и обновит TextView.
Все сохраняем и запускаем приложение. Жмем кнопку Start.
Кнопка Start стала неактивной, т.к. мы ее сами выключили. А TextView обновляется, ProgressBar крутится и кнопка Test нажимается. Т.е. и закачка файлов идет, и приложение продолжает работать без проблем, отображая статус закачки.
Когда все файлы закачаются, кнопка Start снова станет активной.
Подытожим все вышесказанное.
1) Сначала мы попытались грузить приложение тяжелой задачей в основном потоке. Это привело к тому, что мы потеряли экран – он перестал обновляться и отвечать на нажатия. Случилось это потому, что за экран отвечает основной поток приложения, а он был сильно загружен.
2) Мы создали отдельный поток и выполнили весь тяжелый код там. И это бы сработало, но нам надо было обновлять экран в процессе работы. А из не основного потока доступа к экрану нет. Экран доступен только из основного потока.
3) Мы создали Handler в основном потоке. А из нового потока отправляли для Handler сообщения, чтобы он нам обновлял экран. В итоге Handler помог нам обновлять экран не из основного потока.
Достаточно сложный урок получился. Наверняка, мало, что понятно. Не волнуйтесь, в этом уроке я просто показал, в какой ситуации Handler может быть полезен. А методы работы с ним мы рассмотрим подробно в следующих уроках.
На следующем уроке:
— посылаем простейшее сообщение для Handler