Горячая вакансия – релокация в Берлин! Узнать подробности...
Горячая вакансия – релокация в Берлин! Узнать подробности...

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

Посмотреть в Telegram: @AndroidSobes/93
В предыдущих постах мы описали что такое и для чего используются 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().
#OS