Android looper message queue handler

Как реализованы Looper, Handler и MessageQueue?

В предыдущих постах мы описали что такое и для чего используются Looper, Handler, и MessageQueue. Иногда на собеседованиях просят написать свою имплементацию этих сущностей. Хоть эти классы и считаются низкоуровневым Android API, они по большей части реализованы обычными средствами Java.

По своей сути Looper, Handler и MessageQueue реализуют шаблон producer/consumer. Тред-продюсер отправляет сообщения через Handler в коллекцию-буфер, реализованную классом MessageQueue. Тред-потребитель блокирован с помощью класса Looper, который ожидает и принимает сообщения из MessageQueue и передает их на обработку хэндлеру.

Первый этап использования этих сущностей – инициализация лупера, которая выполняется методом Looper.prepare() . Этот метод создает объект-looper вызовом приватного конструктора. При вызове конструктора также создается объект MessageQueue, который хранится в приватном поле класса Looper.

После этого метод prepare() сохраняет созданный объект в статическое поле типа ThreadLocal , имеющее package видимость.

Реализация инициализации лупера довольна простая, но она позволяет в любом месте программы и из любого треда получить лупер и очередь сообщений, связанные с текущим тредом.

Статический метод Looper.myLooper() просто достает лупер из переменной ThreadLocal:

Метод Looper.myQueue() получает лупер методом myLooper() и возвращает поле queue:

В следующем посте разберемся, как реализовано добавление сообщений в очередь.

Тред-продюсер добавляет сообщение в очередь одним из методов post*() или sendMessage*() класса Handler .

Для начала вспомним, что Handler всегда связан с объектом Looper , а значит хэндлер имеет доступ к очереди сообщений ( MessageQueue ) лупера.

Методы post() , postAtTime() , postDelayed() добавляют в очередь сообщений объект Runnable , который будет выполнен тредом-потребителем.
Для этого сначала создается объект Message вызовом приватного метода getPostMessage(Runnable r) . getPostMessage() получает message из пула сообщений методом Message.obtain() и устанавливает runnable в поле callback.

Message.obtain() возвращает объект message из пула, который представляет собой связный список максимальным размером 50 сообщений. Если все сообщения пула используются, то obtain() создает и возвращает новый объект message.

После создания объекта message методы post*() вызывают один из методов sendMessage*() , передавая параметрами созданное сообщение и свои аргументы time или delay .

Вызов метода sendMessage(Message m) делегируется в sendMessageDelayed(m, 0) .

sendMessageDelayed(Message m, long delayMillis) прибавляет значение параметра delayMillis к текущему времени и делегирует вызов в метод sendMessageAtTime(Message m, long uptimeMillis) .

sendMessageAtTime() вызывает приватный метод enqueueMessage() , который устанавливает текущий хэндлер в поле target класса Message и вызывает enqueueMessage() у класса MessageQueue . Этот метод имеет package видимость и не доступен в публичном api.

MessageQueue – это связный список, реализованный с помощью поля next класса Message , которое ссылается на следующее сообщение в списке. Поле next также имеет package видимость.
Сообщения в MessageQueue отсортированы по возрастанию значения поля Message.when. Метод enqueueMessage() проходит по очереди, проверяя значение when каждого из сообщений и вставляет новое сообщение в положенное место очереди.
Код вставки сообщения в очередь в методе enqueueMessage() заключен в synchronized блок, который синхронизирован на this .

В предыдущих постах мы описали инициализацию лупера на потоке-потребителе и реализацию добавления сообщения в очередь потоком-продюсером.
Разберемся, как поток-потребитель получает сообщения из очереди.

Для блокировки потока и ожидания сообщения используется метод loop() .
Метод loop() вызывает метод MessageQueue.next() , который блокирует текущий поток и ожидает появления следующего сообщения.

Метод next() реализует бесконечный цикл, на каждой итерации которого сравнивает текущее время со значением поля when объекта message в голове очереди.
Если SystemClock.uptimeMillis() ≥ msg.when , то next() возвращает сообщение.
Если SystemClock.uptimeMillis() , то поток засыпает на время равное when — uptimeMillis .

Допустим поток вычислил when — uptimeMillis и заснул на минуту. Что будет, если хэндлер добавит в очередь новое сообщение со значением when — uptimeMillis равное 5 секунд, пока поток спит?
При вызове MessageQueue.enqueueMessage() сообщение добавляется в очередь и поток-потребитель пробуждается. Метод next() отрабатывает итерацию, в которой устанавливает новое значение времени пробуждения, равное 5 секундам.

Метод loop() , получив сообщение из next() , передает это сообщение на обработку хэндлеру, вызывая метод dispatchMessage(). Лупер получает хэндлер-обработчик из поля target :

Источник

Многопоточность в Android. Looper, Handler, HandlerThread. Часть 1.

Что вы знаете о многопоточности в андроид? Вы скажете: «Я могу использовать AsynchTask для выполнения задач в бэкграунде». Отлично, это популярный ответ, но что ещё? «О, я слышал что-то о Handler’ах, и даже как то приходилось их использовать для вывода Toast’ов или для выполнения задач с задержкой…» — добавите Вы. Это уже гораздо лучше, и в этой статье мы рассмотрим как и для чего используют многопоточность в Android.

Для начала давайте взглянем на хорошо известный нам класс AsyncTask, я уверен что каждый андроид-разработчик использовал его. Прежде всего, стоит заметить, что есть отличное описание этого класса в официальной документации. Это хороший и удобный класс для управления задачами в фоне, он подойдёт для выполнения простых задач, если вы не хотите тратить впустую время на изучение того как можно эффективно управлять потоками в андроид. Самая главная вещь о которой вы должны знать – только метод doInBackground выполняется в другом потоке! Остальные его методы выполняются в главном UI потоке. Рассмотрим пример типичного использования AsyncTask:

Далее в интерфейсе будем использовать следующий тривиальный макет с прогресбаром для всех наших тестов.

Читайте также:  Настройка фаерфокс для андроида

В макете бесконечно отображается индикация вращения прогресбара. Если прогресбар застынет – это будет что в главном UI потоке происходит выполнение тяжелой работы.

Здесь мы используем AsyncTask, потому что приложению потребуется некоторое время для получения ответа от сервера и мы не хотим что бы нам интерфейс завис в ожидании этого ответа, поэтому мы поручаем выполнить сетевую задачу другому потоку. Есть много постов о том, почему использование AsyncTask – это плохая затея(например: если это внутренний класс вашего активити/фрагмента, то он будет удерживать внутреннюю ссылку на него, что является плохой практикой, потому что активити/фрагмент могут быть уничтожены при смене конфигурации, но они будут висеть в памяти пока работает фоновый поток; если объявлен отдельным статическим внутренним классом и вы используете ссылку на Context для обновления вьюшек, вы должны всегда проверять их на null).

Все задачи в главном потоке выполняются последовательно, делая тем самым код более предсказуемым – вы не рискуете попасть в ситуацию изменения данных несколькими потоками. Значит если какая-то задача работает слишком долго, Вы получите неотвечающее приложени, или ANR(Application Not Responding) ошибку. AsyncTask является одноразовым решением. Класс не может быть повторно использован при повторном вызове execute метода на одном экземпляре – вы непременно должны создать новый экземпляр AsyncTask для новой работы.

Любопытно то, что если вы попытаетесь показать Toast из метода doInBackground, то получите ошибку, содержащую что то вроде:

Из-за чего же мы получили ошибку? Ответ прост: потому что Toast является частью интерфейса и может быть показан только из UI потока, а правильный ответ: потому что он может быть показан только из потока с Looper’ом! Вы спросите, что такое Looper?

Хорошо, пришло время копнуть глубже. AsyncTask отличный класс, но что если его функциональности недостаточно для ваших действий? Если мы заглянем под капот AsyncTask, то обнаружим устройство с крепко связанными компонентами: Handler, Runnable и Thread. Каждый из вас знаком с потоками в Java, но в андройде вы обнаружите ещё один класс HandlerThread, произошедший от Thread. Единственное существенное отличие между HandlerThread и Thread заключается в том что первый содержит внутри себя Looper, Thread и MessageQueue. Looper трудится, обслуживая MessageQueue для текущего потока. MessageQueue это очередь которая содержит в себе задачи, называющиеся сообщениями, которые нужно обработать. Looper перемещается по этой очереди и отправляет сообщения в соответствующие обработчики для выполнения. Любой поток может иметь единственный уникальлный Looper, это ограничение достигается с помощью концепции ThreadLocal хранилища. Связка Looper+MessageQueue выглядит как конвейер с коробками. Задачи в очередь помещают Handler‘ы.

Вы можете спросить: «Для чего вся эта сложность, если задачи всё равно обрабатываются их создателями – Handler‘ами?». Мы получаем как минимум 2 преимущества:

— это помогает избежать состояния гонки (race conditions), когда работа приложения становится зависимой от порядка выполнения потоков;

— Thread не может быть повторно использован после завершения работы. А если поток работает с Looper’ом, то вам не нужно повторно создавать экземпляр Thread каждый раз для работы в бэкграунде.

Вы можете сами создавать и управлять Thread’ами и Looper’ами, но я рекомендую воспользоваться HandlerThread (Google решили использовать HandlerThread вместо LooperThread) : В нём уже есть встроенный Looper и всё настроено для работы.

А что о Handler? Это класс с двумя главными функциями: отправлять задачи в очередь сообщений (MessageQueue) и выполнять их. По умолчанию Handler неявно связывается с потоком в котором он был создан с помощью Looper’a, но вы можете связать его явно с другим потоком, если предоставите ему другой Looper в конструкторе. Наконец-то пришло время собрать все куски теории вместе и взглянуть на примере как всё это работает! Представим себе Activity в которой мы хотим отправлять задачи(в моей статье задачи представлены экземплярами Runnable интерфейса, что такое на самом деле задача(или сообщение) я расскажу во второй части статьи) в очередь сообщений(все активити и фрагменты существуют в главном UI потоке), но они должны выполняться с некоторой задержкой:

Так как mUiHandler связан с главным потоком(он получил Looper главного потока в конструкторе по умолчанию) и он является членом класса, мы можем получить к нему доступ из внутреннего анонимного класса и поэтому можем отправлять задачи-сообщения в главный поток. Мы используем Thread в примере выше и не можем использовать его повторно, если хотим выполнить новую задачу. Для этого нам придется создать новый экземпляр. Есть другое решение? Да! Мы можем использовать поток с Looper’ом. Давайте немного модифицируем пример выше с целью заменить Thread на HandlerThread для демонстрации того, какой прекрасной способностью к повторному использованию он обладает:

Я использую HandlerThread в этом примере, потому что я не хочу управлять Looper’ом сам, HandlerThread сам справится с этим. Один раз мы стартуем HandlerThread, после чего можем отправлять задачи в любое время, но помните о вызове метода quit когда вы хотите остановить HandlerThread. mWorkerHandler связан с MyWorkerThread с помощью его Looper. Вы не сможете инициализировать mWorkerHandler за пределами конструктора HandlerThread , так как getLooper будет возвращать null, потому что поток ещё не существует. Порой вам может встретится следующий способ инициализации Handler:

Порой это отлично работает, но иногда вы будете выхватывать NPE после вызова
postTask, с сообщением о том, что ваш mWorkerHandler — null. Сюрприз!

Почему это произошло? Хитрость здесь в нативном вызове, необходимом для создании нового потока. Если мы взглянем на кусок кода, где вызывается onLooperPrepared, мы найдём следующий фрагмент в HandlerThread классе:

Хитрость заключается в том, что run метод будет вызван только после того, как новый поток создаётся и запускается. И этот вызов может иногда произойти после вызова postTask(можете проверить это самостоятельно, просто поместите точку останова внутри postTask и onLooperPreparerd методов и взгляните, какой из них будет первым), таким образом вы можете стать жертвой состояния гонки между двух потоков (main и background).

Читайте также:  Как сделать превью с андроид

Во второй части разберем как на самом деле внутри MessageQueue работают задачи.

Источник

Android Thread Message Looper Handler Example

Android’s message queue and queue looper are aimed at the specific thread, a thread can have it’s own message queue and queue looper.

1. Android Message Queue And Looper Introduction.

  1. If you want to send messages between different threads, you need to add a message object in that thread’s message queue. Then the queue looper will fetch the message and process it.
  2. In android development, Activity is commonly used as the main thread. Android OS will create a message queue and queue looper for the main thread automatically.
  3. So you can use Handler to send messages to the Activity class to let it modify the UI component.
  4. Because the UI component is thread-unsafe, only the main thread can modify it.
  5. Please read Android Handler Example to learn more.

2. How To Create Child Thread’s Message Queue And Looper.

  1. However, the worker thread created by default has no message queue and message looper, If you want the worker thread to have a message queue and message looper, you can follow the below steps.
  2. Call Looper.prepare() to create the message queue in the thread.
  3. Create a thread-specified Handler that handles messages in the message queue.
  4. Call Looper.loop() to enter the message loop.
  5. If you want the worker thread to quit the message loop, please call Handler.getLooper().quit().

3. Android Child Thread Message Queue And Looper Example.

Источник

Android- Handlers, Loopers, MessageQueue Basics

May 23, 2020 · 6 min read

Probably the most important APIs of Android suited for Multithreading and offloading the tasks to worker threads- Handlers and Loopers.

The Android architecture has the Main Thread AKA UI thread, which updates the UI after every 16ms frame. Failure to update within this window will reflect as the “lag”, and even worse if it fails for 5secs, then the “ Application not responding” shown to the user- marking the app crash. To avoid these types of issues, please resort to offloading the heavy tasks to worker threads.

To explain: th e Android architecture has one main/UI thread which should ideally only be responsible for updating the UI, and other tasks should be done on separate threads(called worker threads). These worker threads upon completion should send the result back to the main thread and the main thread can then use it to update the UI, etc.
There are some Android APIs to do so like Async tasks, Services(Intent services actually), Executors, etc, etc.
But there are even cooler APIs available and are the core of all the above-mentioned APIs- Handlers and Loopers. Even Async and Intent Services rely heavily on them. Let’s see how they work.

Handlers and Loopers Architecture:

For the thread to receive any message from other threads, the most common pattern we follow is that it should read from the common data structure(usually Queues), to which other threads can write. The same is being followed here.

Handler Threads:

These are the threads that can receive messages from any other thread. Main/UI thread is also a Handler thread.
As explained above, such a thread must need a Message Queue(data structure in which other threads can write to), and a mechanism to read this queue infinitely(until stopped).
Looper is a mechanism which loops through the message Queue infinitely until stopped.

Message Queue

Each HandlerThread has one Message Queue into which other threads write data to(via Handlers), and it has a Looper which keeps on reading from it.

Looper:

A Looper is associated with every handler Thread and a Message Queue.
It keeps on reading the MessageQueue for any new Messages, and once it reads a message it will delegate that to the corresponding Callback(Handler).

Handler:

The handler is the exposed API to work with Loopers/MessageQueues. It basically exposes methods to interact with the Looper architecture, like writing messages to MQ, Setting callbacks to handle the message once Looper reads it, etc, etc.
So Each handler will need a Looper(so that it knows in which MQ to write data to since Looper has the reference to the MQ).

Message:

The Messages which can be sent to the HandlerThread. To avoid the number of Messages initializes, there is a pool maintained. Every time the looper is stopped, or the message is read by Looper, it is recycled(i.e. its fields are cleared off), and added to the pool. Next time we want to create a new Message, we get it from the pool( Message.obtain() method does that). This way the Android prevents us to initialize new Message instances every time.

The message is the LinkedList:

Line 1: Its next points to the next Message.
Line 2: It has long when signifying the timeInMillis of when should message be processed/read.

Please note that the LinkedList of these messages should be processed by their when, so it’s sorted on when.
When adding a new msg, we iterate the LinkedList to find the particular slot to insert this message and then insert it there.

Sending Data to UI thread(which is the Handler Thread too):
Say a worker thread(created at line 1) wants to send data(123) to UI thread:

Читайте также:  Програмы вот для андроид

Line 1: Creates a new worker thread.
Line 4: Get the Looper associated with the UI thread
Line 5:Create the Callback which will be called by UI thread, once it reads the message we are going to send to it.
Line 12: Create Handler with the Main Thread’s Looper, so that we write the message to Main thread’s MQ.
Line 13: Create an empty Message from the pool of messages( to avoid GC overload).
Line 14: Put some value into the Empty message — 123.
Line 15: Setting the above handler(which has callback defined) as the Message Target, which will be called once the message is read by the UI looper.
Line 16: Write the Message to the MQ via Main Looper via Handler.

This is pretty easy to write to Main thread from worker thread, as the Main thread is itself a handler thread which has its Looper/MQ etc defined.
But what if we want to write our own Handler Thread to which other threads can communicate?
In that case we need to initialize Loopers, MQ, etc ourselves.

So make a Thread a Handler thread, we need to do:
1. Looper. prepare() → basically initializes the Looper and creates an MQ for it.

2. Looper. loop() → This will start the looper, which will keep on reading the Message Queue until Stopped.

Line 2: This will get the MQ for that Looper.
Line 8: Read from MQ infinitely until stopped(in that case msg read from Queue will be null).
Line 9: gets blocked until the message has arrived in the MQ. queue.next will be blocked until it sends a msg, it will send null only if Looper is stopped(using Looper.quit() method).
Line 15: Once msg is read, send it to its callback(defined in msg.target and hence in Handler’s callback).
Line 17: Once msg is handled, recycle it to clear its data off, and add it to the pool.

These above 2 methods are sufficient to create a Handler Thread.

Another easy way is to extend the Thread by HandlerThread, which under the hood calls these 2 above methods.

This is the HandlerThread run method: which calls Looper.prepare — then Looper.loop and in between onLooperPrepared to do something in this thread.

How to send data to such a Handler thread?

Simple, instead of passing the MaineLooper to the Handler, pass the Worker Handler Thread’s Looper like:

Line 6 is the only difference, here the looper belongs to the Handler thread we created and not that of the default UI thread.

Let’s explore the working of other Methods too:

MessageQueue::next

Looper.loop depends heavily on Queue.next() method. Below is the stripped-down version of next:

MessageQueue has the LinkedList of Messages(sorted on Message.when). Message.when corresponds to the time this message should be processed.

Line 6: Get the LinkedList of messages written into the Queue.
Line 7 and 8: If the message exists and message read has when before the current time(I.e. it is eligible to be processed), read it.
Line 10 and 11: LinkedList points to the next message now, as this message is read and could be removed from the Linked list(its next is set to null).
line 12: set this message in use, so that no further action can be done on it, like updating it, etc.
line 13: return it to the Looper. This will not happen until any message is read, as there is an infinite loop enclosing this code at line 2.
Line 17 and 18: Returns null only if it is stopped.

Adding Messages to Queue:

This is done via handler.sendMessage() or Handler.post()

2 types of messages can be sent to HandlerThread:
a. Messages: which have when, data, target, etc fields. Upon being read by the Looper, it delegates it to msg.target to handle the message. This will run on the HandlerThread, and not on the worker thread which sends this message.
b. Runnable: Upon read by the Looper, the Runnable.run() is called inside the Handler Thread. Under the hood this Runnable is converted into the empty message, and this runnable is set as its callback. So that this runnable also acts as a Message.

Line 2 wraps this Runnable into the Message, and its the Message which gets written into the MQ.

As shown, this basically calls queue.enqueueMessage to write this message into the mQueue(MessageQueue for this handler thread).

MessageQueue.enqueueMessage(Message )

Line 2: If it’s already being read(next() sets this boolean true), or already added into Queue(line 13), then don’t add the same message again.
Line 7–10: If the Queue/Looper is quitting, then don’t add msgs into it.
Line 13: Set this msg to be in use so that it’s not added into the queue again.
Line 16: If this msg is to be processed before or at the current time, add it at the front.
Line 17–18: LinkedList code to add msg at the head.
Line 21–27: Find the slot to add this msg based on its when, as the LL is sorted on its when. This is a general LinkedList iteration code.

Please note that all the above methods are stripped-down to ease the understanding.

Источник

Оцените статью