Класс 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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Урок 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
Что такое Method Handles в Java
1. Вступление
В этом туториале мы рассмотрим важный API, представленный в Java 7 и расширенный в новых версиях, java.lang.invoke.MethodHandles.
Мы узнаем, что такое method handles, как их создавать и использовать.
2. Что такое Method Handles?
В документации API method handle имеет такое определение:
Method handle — это типизированная, исполняемая ссылка на базовый метод, конструктор, поле или другую низкоуровневую операцию с дополнительными трансформациями аргументов или возвращаемых значений.
Другими словами, method handles — это низкоуровневый механизм для поиска, адаптации и вызова методов. Объекты method handles неизменяемые и не имеют отображаемого состояния.
Для создания и использования MethodHandle нужно выполнить 4 действия:
2.1. Method Handles vs Reflection
Method handles были представлены для функционирования наряду с java.lang.reflect API, т.к. они созданы для разных целей и отличаются по своим характеристикам.
С точки зрения производительности, MethodHandles API может оказаться намного быстрее Reflection API, поскольку проверки доступа выполняются во время создания, а не исполнения. При наличии security manager’а это различие увеличивается, т.к. поиск классов и получение их элементов подвергаются дополнительным проверкам.
Однако, производительность — не единственный показатель оптимальности задачи, нужно учитывать, что MethodHandles API сложнее в использовании из-за недостатка таких механизмов, как получение методов класса, проверка маркеров доступа и др.
Несмотря на это, MethodHandles API дает возможность каррировать методы, менять тип и порядок параметров.
Теперь, зная определение и предназначение MethodHandles API, можем работать с ними. Начнем с поиска методов.
3. Создание Lookup
Первое, что нужно сделать, когда мы хотим создать method handle, — это получить lookup, объект-фабрику, отвечающий за создание method handles для методов, конструкторов и полей, видимых для класса lookup.
С помощью MethodHandles API можно создать lookup-объект с разными режимами доступа.
Создадим lookup, предоставляющий доступ к public-методам:
Однако, если нам нужен доступ к методам private и protected, вместо этого мы можем использовать метод lookup():
4. Создание MethodType
Для создания MethodHandle lookup-объекту необходимо задать тип, и это можно сделать с помощью класса MethodType.
В частности, MethodType представляет аргументы и тип возвращаемого значения, принимаемые и возвращаемые method handle, или передаваемые и ожидаемые вызывающим кодом.
Структура MethodType проста, она формируется возвращаемым типом вместе с соответствующим числом типов параметра, которые должны полностью соотноситься между method handle и вызывающим кодом.
Так же, как и MethodHandle, все экземпляры MethodType неизменяемы.
Посмотрим, как определить MethodType, задающий класс java.util.List в качестве типа возвращаемого значения и массив Object в качестве типа ввода данных:
Определим MethodType, который возвращает значение int и принимает Object:
Можно приступать к созданию MethodHandle.
5. Поиск MethodHandle
После того, как мы задали тип метода, для создания MethodHandle нужно найти его с помощью объекта lookup или publicLookup, который также выдает исходный класс и имя метода.
Lookup предоставляет набор методов, позволяющий находить method handle оптимальным способом с учетом области видимости метода. Рассмотрим основные подходы, начиная с простейших.
5.1. Method Handle для методов
С помощью метода findVirtual() можно создать MethodHandle для метода экземпляра. Создадим его на основе метода concat() класса String :
5.2. Method Handle для статических методов
Для получения доступа к статическому методу можно использовать метод findStatic() :
5.3. Method Handle для конструкторов
Создадим method handle с поведением, как у конструктора класса Integer с параметром String:
5.4. Method Handle для полей
С помощью method handle можно также получить доступ к полям.
Начнем с определения класса Book:
В качестве исходного условия мы имеем прямую видимость между method handle и объявленным свойством, таким образом, можно создать method handle с поведением как у get-метода:
Более подробную информацию об управлении переменными/полями ищите в статье Java 9 Variable Handles Demystified, где мы рассказываем о java.lang.invoke.VarHandle API, введенном в Java 9.
5.5. Method Handle для Private методов
Создать method handle для метода типа private можно с помощью java.lang.reflect API.
Начнем с того, что создадим private метод для класса Book:
Теперь мы можем создать method handle с поведением метода formatBook() :
6. Вызов Method Handle
6.1. Вызов Method Handle
При использовании метода invoke() количество аргументов (arity) фиксируется, но при этом возможно выполнение приведения типов и упаковка/распаковка аргументов и типов возвращаемого значения.
Теперь посмотрим, как можно использовать invoke() с упакованным аргументом:
6.2. Вызов с аргументами
Вызов method handle с помощью метода invokeWithArguments имеет меньше всего ограничений.
По сути, помимо проверки типов и упаковки/распаковки аргументов и возвращаемых значений, он позволяет делать вызовы с переменным числом параметров.
На практике мы можем создать список Integer, имея массив значений int неизвестной длины:
6.3. Вызов Exact
Фактически, он не предоставляет возможность приведения типов класса и требует фиксированного набора аргументов.
Посмотрим, как можно выполнить сложение двух значений int с помощью method handle:
7. Работа с массивами
MethodHandles могут работать не только с полями и объектами, но и с массивами. При помощи asSpreader() API можно создать method handle, поддерживающий массивы в качестве позиционных аргументов.
В этом случае method handle принимает массив, распределяя его элементы как позиционные аргументы, и опционально — длину массива.
Посмотрим, как получить method handle, чтобы проверить, являются ли аргументы массива одинаковыми строками:
8. Уточнение Method Handle
Как только method handle задан, можно уточнить его, привязав к аргументу, без вызова метода.
Например, в Java 9 этот трюк используется для оптимизации конкатенации строк.
Посмотрим, как можно выполнить конкатенацию, привязав суффикс к concatMH :
9. Обновления Java 9
В Java 9 было внесено несколько изменений в MethodHandles API, чтобы упростить их использование.
Обновления касаются 3 основных аспектов:
Эти изменения повлекли за собой другие полезные нововведения:
Более подробный список изменений доступен в MethodHandles API Javadoc.
10. Заключение
В этой статье мы познакомились с MethodHandles API, а также узнали, что из себя представляют Method Handles и как их использовать.
Мы также описали, как он связан с Reflection API. Так как вызов method handles это довольно низкоуровневая операция, их использование оправдано только в том случае, если они в точности подходят под ваши задачи.
Как обычно, весь исходный код для статьи доступен на Github.
Находки программиста
Решения конкретных задач программирования. Java, Android, JavaScript, Flex и прочее. Настройка софта под Linux, методики разработки и просто размышления.
пятница, 24 января 2014 г.
Всем привет, хочу поделиться методологией применения инструмента созданного облегчить разработку приложений (на сцену выходит Handler).
Случай №1:
Случай №2. Реализация вызова onPostExecute в AsyncTask:
private Result postResult(Result result) <
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult (this, result));
message.sendToTarget();
return result;
>
private void finish(Result result) <
if (isCancelled()) <
onCancelled(result);
> else <
onPostExecute(result);
>
>
private static class InternalHandler extends Handler <
public void handleMessage(Message msg) <
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) <
case MESSAGE_POST_RESULT:
result.mTask.finish(result.mData[0]);
break;
.
>
>
>
Пример обработки сообщений:
Handle handler = new Handler() <
public void handleMessage(Message msg) <
switch (msg.what) <
.
>
>
>;
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
После чего сообщение будет отправлено на обработку в метод handleMessage.
Все сообщения «стоят» в очереди в android.os.MessageQueue который принимает решение какое сообщение и на какой target необходимо передать для обработки.
И так.
И реализация:
class HandlerExample <
private Handler mWorkHandler;
private HandlerThread mHandlerThread;
public enum State <
stopped,
starting,
started,
stopping
>
private State mState;
private final Runnable mActionNetworkCheck = new Runnable() <
@Override
public void run() <
// проверяем данные на сервере
if (isItWork()) <
mWorkHandler.postDelayed(mActionNetworkCheck, 10000);
>
>
>;
private final Runnable mActionWifiScan = new Runnable() <
@Override
public void run() <
// работа со сканом сетей
if (isItWork()) <
mWorkHandler.postDelayed(mActionWifiScan, 30000);
>
>
>;
private final Runnable mActionUsbIo = new Runnable() <
@Override
public void run() <
// работа по usb хосту
if (isItWork()) <
mWorkHandler.postDelayed(mActionUsbIo, 4000);
>
>
>;
private synchronized boolean isItWork() <
return mState == State.started;
>
public void startup() <
synchronized (this) <
if (mState == State.stopped) <
mState = State.starting;
> else <
throw new IllegalStateException(«Example must be stopped for start»);
>
>
mHandlerThread = new HandlerThread(«WorkHandlerThread») <
@Override
protected void onLooperPrepared() <
mWorkHandler = new Handler(getLooper());
synchronized (HandlerExample.this) <
mState = HandlerExample.State.started;
>
mWorkHandler.post(mActionNetworkCheck);
mWorkHandler.post(mActionWifiScan);
mWorkHandler.post(mActionUsbIo);
>
>;
mHandlerThread.start();
>
public void shutdown() <
synchronized (this) <
if (mState == State.started) <
mState = State.stopping;
> else <
throw new IllegalStateException(«Example must be started for shutdown»);
>
>
mHandlerThread.quit();
try <
mHandlerThread.join();
> catch (InterruptedException e) <
throw new RuntimeException(e);
>
mWorkHandler = null;
mHandlerThread = null;
synchronized (this) <
mState = State.stopped;
>
>
>
Выводы: организовать работу многих событий можно и в один поток если при этом временные рамки не жесткие. Конечно можно вспомнить о java.util.Timer и TimerTask’e, но ИМХО Handler более удобный.
Спасибо всем кто дочитал до этой строчки!















