В предыдущих постах мы описали что такое и для чего используются
Looper,
Handler, и
MessageQueue. Иногда на собеседованиях просят написать свою имплементацию этих сущностей. Хоть эти классы и считаются низкоуровневым Android API, они по большей части реализованы обычными средствами Java.
По своей сути Looper, Handler и MessageQueue реализуют
шаблон producer/consumer. Тред-продюсер отправляет сообщения через Handler в коллекцию-буфер, реализованную классом MessageQueue. Тред-потребитель блокирован с помощью класса Looper, который ожидает и принимает сообщения из MessageQueue и передает их на обработку хэндлеру.
Первый этап использования этих сущностей – инициализация лупера, которая выполняется методом
Looper.prepare()
. Этот метод создает объект-looper вызовом приватного конструктора. При вызове конструктора также создается объект MessageQueue, который хранится в приватном поле класса Looper.
После этого метод
prepare()
сохраняет созданный объект в статическое поле типа
ThreadLocal<Looper>, имеющее package видимость.
Реализация инициализации лупера довольна простая, но она позволяет в любом месте программы и из любого треда получить лупер и очередь сообщений, связанные с текущим тредом.
Статический метод
Looper.myLooper()
просто достает лупер из переменной ThreadLocal:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Метод
Looper.myQueue()
получает лупер методом
myLooper()
и возвращает поле queue:
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
В следующем посте разберемся, как реализовано добавление сообщений в очередь.
Тред-продюсер добавляет сообщение в очередь одним из методов
post*()
или
sendMessage*()
класса
Handler
.
Для начала
вспомним, что
Handler
всегда связан с объектом
Looper
, а значит хэндлер имеет доступ к очереди сообщений (
MessageQueue
) лупера.
Методы
post()
,
postAtTime()
,
postDelayed()
добавляют в очередь сообщений объект
Runnable
, который будет выполнен тредом-потребителем.
Для этого сначала создается объект
Message
вызовом приватного метода
getPostMessage(Runnable r)
.
getPostMessage()
получает message из пула сообщений методом
Message.obtain() и устанавливает runnable в поле
callback.
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
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() < msg.when
, то поток засыпает на время равное
when - uptimeMillis
.
Допустим поток вычислил
when - uptimeMillis
и заснул на минуту. Что будет, если хэндлер добавит в очередь новое сообщение со значением
when - uptimeMillis
равное 5 секунд, пока поток спит?
При вызове
MessageQueue.enqueueMessage()
сообщение добавляется в очередь и поток-потребитель пробуждается. Метод
next()
отрабатывает итерацию, в которой устанавливает новое значение времени пробуждения, равное 5 секундам.
Метод
loop()
, получив сообщение из
next()
, передает это сообщение на обработку хэндлеру, вызывая метод
dispatchMessage(). Лупер получает хэндлер-обработчик из поля
target
:
msg.target.dispatchMessage(msg)
Метод
Handler.dispatchMessage(Message msg)
проверяет, установлено ли у message поле
callback. В случае если сообщение имеет колбэк, хэндлер запускает его вызовом
run()
. Если колбэк равен
null
, хэндлер передает сообщение на обработку методу
handleMessage(). По-умолчанию
handleMessage()
не делает ничего, и реализация этого метода
отдается пользователям хэндлера.
Код метода
loop()
заключен в бесконечный цикл, поэтому после обработки сообщения, выполнение возвращается к вызову
MessageQueue.next()
и ожиданию следующего сообщения.
Лупер ожидает и обрабатывает сообщения, пока не будет вызван метод
quit()
или
quitSafe()
.