Хэндлер или хендлер что это в программировании
Класс 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 и зачем он нужен
Для полного понимания урока желательно иметь представление о потоках (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 может быть полезен. А методы работы с ним мы рассмотрим подробно в следующих уроках.
Eclipse может подчеркивать Handler желтым цветом и ругаться примерно такими словами: «This Handler class should be static or leaks might occur«. Тем самым он сообщает нам, что наш код немного плох и может вызвать утечку памяти. Тут он прав абсолютно, но я в своих уроках все-таки буду придерживаться этой схемы, чтобы не усложнять.
На следующем уроке:
— посылаем простейшее сообщение для Handler
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Русские Блоги
Краткий анализ принципов Handler, MessageQueue, Looper и Thread
1. Обработчик официального введения
Обработчик имеет две основные цели: (1) выполнять сообщения и выполняемые объекты в определенный момент в будущем, (2) выполнять операции, выполняемые в разных потоках.
Планирование сообщений выполняется с помощью методов post (Runnable), postAtTime (Runnable, long), postDelayed (Runnable, Object, long), sendEmptyMessage (int), sendMessage (Message), sendMessageAtTime (Message, long) и sendMessageDelayed (Message, long) из. Почтовая версия позволяет вам вызывать объекты Runnable в очереди принятых сообщений (Message), версия sendMessage позволяет обрабатывать сообщение (Message), содержащее данные пакета (данные пакета), методом handleMessage (Message) обработчика (требуется реализация Подкласс Handler).
При доставке или отправке сообщения в обработчик вы можете разрешить обработку контента сразу после того, как очередь сообщений будет готова, или отложить обработку или обработку в указанное время. Последние два метода позволяют вам реализовать тайм-ауты, тайминг и другие временные поведения.
Когда процесс создается для приложения, его основной поток будет использоваться для запуска очереди сообщений, которая отвечает за управление объектами приложения верхнего уровня (активность-действия, широковещательный приемник-broadcastReceiver и т. Д.) И любыми окнами, которые они создают. Вы можете создать свой собственный поток и общаться с основным потоком приложения через обработчик. Это связь между потоками путем вызова метода post или sendMessage. Затем данный Runnable или сообщение будет размещен в очереди сообщений обработчика и обработан в соответствующее время.
Ссылка на официальный сайт: https://developer.android.google.cn/reference/android/os/Handler
2. Принцип Хендлера
2.1 Сначала мы должны понять, что такое Handler и его назначение
Смотрите: 1. Официальное введение обработчика
2.2. Понимать связанные классы, используемые в Handler (Handler, Message, MessageQueue, Looper, Thread)
2.2.1 Описание базовой ассоциации класса, связанного с обработчиком
Из рисунка выше видно, что Thread использует основу всего механизма Handler, то есть Looper и MessageQueue построены на Thread, Handler построен на Looper и MessageQueue, а при вызове Handler он косвенно вызывает низкоуровневые классы. Выполнить соответствующее задание;
2.2.2.MessageQueue (очередь сообщений)
Почему существует очередь сообщений? Например, у нас есть несколько потоков, которые загружают файлы в фоновом режиме. Когда несколько потоков, которые загружают файлы в фоновом режиме, загружаются одновременно, они должны уведомить приемную, что загрузка завершена. Поскольку поток может обрабатывать только одну вещь за раз, какое уведомление должно отображаться первым Это также вводит понятие очереди сообщений, мы сохраняем каждое уведомление о завершении загрузки в сообщении, а затем добавляем его в очередь сообщений; поток по очереди извлекает сообщение из очереди сообщений, а затем обрабатывает их по очереди; сообщение можно понимать как Событие, которое должно быть обработано потоком, MessageQueue понимается как пул сообщений, которые должны быть обработаны потоком;
2.2.3.Looper
Я закончил говорить об очереди сообщений MssageQueue, так что же заставляет работать очередь сообщений, то есть Looper, тогда как Looper реализует ее?
Класс Looper использует метод цикла сообщений в потоке, чтобы запустить MessageQueue. Когда мы создаем поток, очередь сообщений, соответствующая потоку, не создается по умолчанию. Разработчикам необходимо вызвать метод Looper.prepare (), а затем вызвать метод Looper.loop (); Типичный пример потока Looper, сопровождаемый Looper, использующим методы preapre () и loop () для создания взаимодействующего Hander.
Метод preapre и метод цикла должны вызываться в методе run путем просмотра исходного кода.
Видно, что метод конструирования Looper является частным методом конструирования, и ему не разрешается создавать новый объект Looper извне, что косвенно указывает на то, что Looper и поток имеют отношение один к одному, только через Метод Looper.myLooper () возвращает Looper, связанный с текущим потоком;
Введение в метод Looper.prepare ()
Метод prepare в основном отвечает за подготовительную работу. Метод loop () может быть вызван только после завершения подготовительной работы. Итак, какая подготовительная работа была выполнена?
Вызов sThreadLocal.get () сначала определяет, был ли поток связан с Looper. Если он связан, будет выдано исключение RuntimeException, указывающее на то, что поток не может быть связан с несколькими Looper, а затем вызывается новый Looper (quitAllowed) для создания нового объекта Looper. Затем установите связь между Looper и потоком с помощью метода sThreadLocal.set (новый Looper (quitAllowed));; установите привязку Looper и sThreadLocal с помощью метода sThreadLocal.set;
Это завершает двустороннюю привязку потоков sThreadLocal и Looper:
А. Поток, связанный с Looper, может быть получен через sThreadLocal в Looper;
B. Поток sThreadLocal может получить объект Looper, связанный потоком, через метод sThreadLocal.get ().
Через конструктор Looper вы также можете понять, как связаны Looper и MessageQueue:
Через код конструктора вы можете видеть, что новый создается в конструкторе Looper, и объект MessageQueue назначается объекту mQueue одновременно, то есть устанавливается связь между Looper и MessageQueue;
Примечание: метод Looper.prepare () может быть вызван только один раз, если он вызывается несколько раз, это вызовет исключение во время выполнения;
После вызова метода prepare работа по подготовке завершена, и метод loop () может быть вызван в связанном друг с другом потоке. Что тогда делается в методе loop?
Код метода цикла выглядит следующим образом:
Ниже приведены ключевые моменты выполнения метода цикла:
a. final MessageQueue queue = me.mQueue;
Вызовите myLooper, чтобы привязать Looper к текущему потоку, а затем получить очередь сообщений mQueue в Looper;
Условия не установлены, бесконечный цикл;
c. Message msg = queue.next();
Мы вынимаем сообщение из очереди сообщений с помощью следующего метода очереди сообщений. Если в это время в очереди сообщений есть сообщение, следующий метод немедленно вернет сообщение. Если в данный момент в очереди сообщений нет сообщений, то следующий метод заблокирует следующий метод. Ожидание, чтобы получить сообщение. к
Отправьте сообщение соответствующему обработчику для обработки, цель относится к обработчику, связанному с обработкой сообщения
2.2.3Handler
Обработчик имеет несколько конструкторов следующим образом:
a. publicHandler()
b. publicHandler(Callback callback)
c. publicHandler(Looper looper)
d. publicHandler(Looper looper, Callback callback)
Конструкторы a и b не передают Looper через метод Looper.myLooper (); чтобы привязать Looper к текущему потоку;
Методы c и d могут быть переданы в объект Looper и сохранены в объект mLooper;
Если в методе построения нет входящего Callback, вам необходимо переопределить метод handleMessage в классе Handler. При передаче используйте метод handleMessage в Callback для обработки сообщения, аналогично использованию метода run в потоке и использованию нового потока (Runnable) в новом потоке () Метод run в Runnable;
После разговора о конструкторе Handler, мы сосредоточимся на связанных методах того, как Handler отправляет сообщения.Handler отправляет сообщения в основном в двух формах, одна из них sendXXX () и postXXX ():
Введение в методы, относящиеся к sendXXX, исходный код:
Вы обнаружите, что sendMessageXXX и sendEmptyMessageXXX в конечном счете вызывают метод sendMessageAtTime для помещения сообщения в очередь;
Введение в методы, связанные с postXXX, исходный код:
исходный код getPostMessage
Глядя на метод getPostMessage, мы можем обнаружить, что этот метод, наконец, генерирует объект Message, объект Message переносит объект обратного вызова, и, наконец, метод postXXX передает сообщение, содержащее информацию Runnable, методу sendMessageXXX и, наконец, вызывает sendMessageAtTime, чтобы добавить сообщение в очередь сообщений. Анализ кода показывает, что метод postXXX в конечном итоге зависит от метода sendMessageXXX;
Мы используем следующую диаграмму, чтобы сделать связь между postXXX и sendMessageXXX более ясной:
Анализируя исходный код, мы обнаружим, что sendMessageXXX, sendEmptyMessageXXX и postXXX в конечном счете называются методами sendMessageAtTime, поэтому давайте взглянем на исходный код метода sendMessageAtTime:
a.mQueue получает Looper, связанный текущим потоком, а затем получает очередь сообщений, хранящуюся в Looper;
Метод b.enqueueMessage () предназначен для добавления сообщения в очередь, исходный код которого выглядит следующим образом:
Соответствующая привязка является текущим обработчиком;
В соответствии с очередью сообщений, связанной с обработчиком, вызовите метод enqueueMessage () очереди сообщений, чтобы добавить сообщение Message в очередь;
Анализируя приведенный выше исходный код, я разберу всю блок-схему следующим образом:
Выше приведен весь поток операций обработчика, получающего сообщение в очередь;
Давайте посмотрим, как Хандер выбирает сообщения из очереди сообщений и обрабатывает их:
Когда мы проанализировали исходный код, мы обнаружили, что Looper имеет механизм цикла сообщений. После вызова метода цикла он будет непрерывно извлекать сообщения из пары сообщений. Проверяя метод цикла, мы видим, что следующий код будет вызван после окончательного получения сообщения msg.target.dispatchMessage (msg ), наконец, вызовите метод dispatchMessage (msg) обработчика, связанного с Message, для обработки сообщения, и просто проанализируйте исходный код dispatchMessage:
Сначала проверьте, есть ли в сообщении информация Runnable, если это так, вызовите метод run в потоке для выполнения msg.callback.run (). Этот метод предназначен для добавления сообщения в очередь сообщений с помощью postXXX;
Б. Проверьте, что Сообщение не добавлено в очередь сообщений с помощью postXXX, то есть в случае Message.callback == null, и решите, следует ли реализовать метод Callback в классе Handler.
Если это реализовано, вызовите метод handleMessage в Callback для обработки сообщения;
В. Если мы не передадим объект типа Callback в конструктор, то mCallback будет нулевым, тогда мы вызовем метод hanldeMessage самого обработчика, который по умолчанию является пустым методом, и нам нужно переписать и реализовать этот метод самостоятельно.
Исходя из описанной выше ситуации, мы обнаружим, что обработка сообщений будет иметь различия в приоритетах: сначала проверьте, добавлено ли оно в очередь сообщений с помощью метода postXXX, а затем проверьте, реализован ли интерфейс обратного вызова, и, наконец, вызовите метод Handler.handleMessage () для обработки сообщения;
3. Блок-схема механизма погрузчика
Выше приведена моя блок-схема, основанная на просмотре сводки исходного кода, пожалуйста, исправьте меня;
Урок 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