В предыдущих постах мы описали что такое и для чего используются
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().