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