Handler, Looper, события в Android.

Наверное все из вас знают что методы onCreate, onStart, onResume, onPause, on Stop, onDestroy в Android описывают некие события происходящие во время работы запущенного приложения. У Activity, Fragment или у View компонента имеется свой жизненный цикл во время которого происходят разные события. Те разработчики, кто имел опыт разработки собственных View компонентов могли также сталкиваться с методами onDraw, onMeasure.

На самом деле никаких событий не существует. Давайте рассмотрим почему так получилось, на примере работы клавиатуры. Вам скорее всего может показаться, что как только мы нажимаем клавишу на клавиатуре, мгновенно происходит действие на экране. Так ли это на самом деле? Короткий ответ — нет! В современных клавиатурах присутствует контроллер, который опрашивает клавиши. Обычно с частотой около 1 Мгц, что приблизительно равняется 1000 раз в секунду. Контроллер запоминает состояние клавиши, проходит следующий цикл опроса, он видит что состояние изменилось и отправляет информацию о событии нажатия клавиши в операционную систему. Теоретически можно представить что есть человек, который делает нажатия с большей частотой. В этом случае нажатия не будут обрабатываться операционной системой. Если контроллер не успеет сделать опрос, то мы не получим информацию о событии. В операционной системе Android все устроено похожим образом.

Давайте начнем с сообщений. Они в Android представлены классом android.os.Message.

C этими сообщениями в системе работает Looper. По сути у данного класса есть один главный метод. Это метод loop().

Внутри Looper класса в методе loop() есть бесконечный цикл. В этом цикле из очереди берется сообщение. Сообщение представлено объектом Message, который мы рассмотрели ранее. После чего проверяется наличие сообщения. В случае если оно есть, то вызывается метод

msg.target.dispatchMessage(msg)

Вернувшись назад в код класса android.os.Message мы можем увидеть что target это объект класса Handler

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

Если коротко то, у нас есть бесконечный цикл, в него передаются сообщения, эти сообщения выполняются операционной системой. Для того чтобы понять что происходит в этой цепочке необходимо также познакомиться с тем, что же из себя представляет Handler на самом деле? Handler это инструмент нужный для того чтобы передавать сообщения в Looper. У него есть методы post, postDelayed, sendMessage, куча разных дополнительных перегруженных методов. Но в данной статье нам больше всего интересен метод dispatchMessage(Message msg)

Собственно это метод, который получает сообщения и выполняет их. Вот пример как в Android выглядит ViewRootHandler. В самой операционной системе есть множество таких Handler’ов для выполнения различных задач.

Давайте теперь коротко сформулируем что у нас получилось. Во-первых у нас есть Handler’ы которые упаковывают сообщения и кладут их в MessageQueue. Looper крутит в бесконечном цикле очередь из таких сообщений, достает сообщения и отправляет их на выполнение в Handleer’ы.

Картинки по запросу "Looper handler"

В принципе общая идея понятна. Теперь давайте рассмотрим HandlerThread.

HandlerThread это готовый инструмент, который объединяет все наши рассматриваемые сущности вместе. В нем есть готовый Looper, в нем он и запускается, внутри метода run(). К слову Looper здесь Thread local переменная. Что означает что он доступен отдельно для каждого потока.

Теперь обладая общей информацией о том как все это работает попробуйте предположить что произойдет в этом участке кода? Сколько раз будет напечатан лог?

Правильный ответ — ни разу! Суть в том, что все это выполняется в UI потоке. UI поток представляет собой HandlerThread, в HandlerThread’e есть Looper который выполняет цепочку сообщений. Мы переходим к методу Looper.loop(), у нас повторяется вызов бесконечного цикла. То есть, находясь в бесконечном цикле, мы опускаемся еще на один уровень. После этого выполнение будет прервано и будет выполнен следующий Message из цепочки. Как все это используется? Для чего нужны HandlerThread’ы?

HandlerThread’ы нужны чтобы передавать сообщения между потоками. Это основной инструмент работы с многопоточностью в Android. Наверняка вы явно никогда с ним не взаимодействовали. Хотя если вы к примеру вызывали у View компонента метод post, то это тоже отправка сообщения на обработку в Handler. Если вы использовали сторонние библиотеки, такие как RxJava или Kotlin Coroutines, вы наверняка переключали потоки во время работы. Все это внутри работает через Handler и Looper главного потока. Когда в RxJava вы пишите AndroidSchedulers.mainThread() то вы получаете обертку над Handler’ом и Looper’ом главного потока.

Теперь давайте взглянем как это работает в Android. Как вообще стартует Android приложение?

Мы знаем что в своей сущности Android приложение — это Java приложение. В Java приложении должен быть метод main(). C него начинается старт любого приложения.

Изначально у нас есть Zygote. Это компонент Android системы. После запуска у нас происходит процесс отделения процесса от Zygote. Создается клон, отбираются все права, на уровне ядра присваивается пользователь приложения(Каждое приложение в Android с точки зрения системы это отдельный пользователь), это позволяет на уровне ядра Linux реализовать управление правами и изоляцию приложения друг от друга. После этого приложение помещается в оболочку в которой уже есть ActivityThread и оттуда оно запускается. Далее запускается Looper, ловится первое сообщение и начинается работа приложения. Например старт первого Activity. Мы видим что в методе main поднимается Looper и после этого цикл уже не может быть никак завершен, кроме падения с ошибкой.

В Android есть Handler’ы по умолчанию. Вот некоторые из них

  • android.os.Handler — основной системный Handler
  • android.view.ViewRootImpl — события отрисовки View
  • android.view.Choreographer — события кликов
  • android.view.inputmethod.InputMethodManager — события управления вводом
  • android.app.ActivityThread — получает события жизненного цикла Activity и UI — thread.

Возможно это кому — то может понадобиться для более детальной отладки приложений, но в Android можно подписаться и наблюдать за простоем приложения через подписку на события

Looper.myQueue.addIdleHandler { return@addIdleHandler true }

или же логгировать все события пришедшие в Main Looper.

Looper.getMainLooper().setMessageLogging { Log.d(«Looper», it) }

Понравилась статья? Поделиться с друзьями:
Комментарии: 1
  1. Алексей

    Спасибо за информацию.

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: