Какая связь между Looper, Handler и MessageQueue в Android?

95

Я проверил официальный Android документации / руководство для Looper, Handlerи MessageQueue. Но я не мог этого понять. Я новичок в Android и очень запутался в этих концепциях.

Блейк
источник

Ответы:

103

A Looper- это цикл обработки сообщений: он читает и обрабатывает элементы из MessageQueue. LooperКласс, как правило , используется в сочетании с HandlerThread(подкласс Thread).

A Handler- это служебный класс, который облегчает взаимодействие с a - в Looperосновном путем отправки сообщений и Runnableобъектов в поток MessageQueue. Когда Handlerсоздается, он привязывается к определенному Looper(и связанному потоку и очереди сообщений).

При типичном использовании вы создаете и запускаете, а HandlerThreadзатем создаете Handlerобъект (или объекты), с помощью которого другие потоки могут взаимодействовать с HandlerThreadэкземпляром. HandlerДолжны быть созданы во время работы на HandlerThread, хотя после создания не существует никаких ограничений на то , что потоки могут использовать Handler«ы методы планирования ( post(Runnable)и т.д.)

Основной поток (он же поток пользовательского интерфейса) в приложении Android настраивается как поток-обработчик до создания экземпляра вашего приложения.

Помимо класса Docs, есть хорошая дискуссия все это здесь .

PS Все вышеперечисленные классы находятся в пакете android.os.

Тед Хопп
источник
@Ted Hopp - Отличается ли очередь сообщений Looper от очереди сообщений Thread?
CopsOnRoad 03
2
@Jack - Это одно и то же. В Android Docs API дляMessageQueue государства , что MessageQueueявляется « класс низкого уровня , удерживающий список сообщений , которые будут Отправляются Looper. »
Тед HOPP
95

Широко известно, что в Android незаконно обновлять компоненты пользовательского интерфейса непосредственно из потоков, отличных от основного потока . Этот документ Android ( Обработка дорогостоящих операций в потоке пользовательского интерфейса ) предлагает шаги, которые необходимо выполнить, если нам нужно запустить отдельный поток для выполнения некоторых дорогостоящих работ и обновить пользовательский интерфейс после того, как это будет сделано. Идея состоит в том, чтобы создать объект Handler, связанный с основным потоком , и опубликовать в нем Runnable в соответствующее время. Это Runnableбудет вызвано в основном потоке . Этот механизм реализован с помощью классов Looper и Handler .

LooperКласс поддерживает MessageQueue , который содержит список сообщений . Важным признаком Looper является то, что он связан с потоком, в котором Looperсоздается . Эта ассоциация сохраняется навсегда и не может быть нарушена или изменена. Также обратите внимание, что поток не может быть связан более чем с одним Looper. Чтобы гарантировать эту связь, Looperона хранится в локальном хранилище потока и не может быть создана напрямую через конструктор. Единственный способ создать его - вызвать статический метод подготовкиLooper . Метод подготовки сначала исследует ThreadLocalтекущего потока, чтобы убедиться, что с потоком еще не связан Looper. После обследования создается новый Looperи сохраняется в формате ThreadLocal. Подготовив Looper, мы можем вызвать для него метод цикла, чтобы проверить наличие новых сообщений и Handlerразобраться с ними.

Как видно из названия, Handlerкласс в основном отвечает за обработку (добавление, удаление, отправку) сообщений текущего потока MessageQueue. HandlerЭкземпляр также связан с резьбой. Связывания между обработчиком и нити достигается с помощью Looperи MessageQueue. HandlerБудет всегда привязан кLooper , и впоследствии связывается с нитью , связанной с Looper. В отличие от этого Looper, несколько экземпляров Handler могут быть привязаны к одному потоку. Каждый раз, когда мы вызываем post или другие подобные методы в Handler, новое сообщение добавляется в связанный MessageQueue. В поле назначения сообщения устанавливается текущий Handlerэкземпляр. КогдаLooperПолучив это сообщение, он вызывает dispatchMessage в целевом поле сообщения, чтобы сообщение возвращалось к экземпляру Handler для обработки, но в правильном потоке. Отношения между Looper, Handlerи MessageQueueкак показано ниже:

введите описание изображения здесь

K_Anas
источник
5
Спасибо! но какой смысл, если обработчик сначала отправит сообщение в очередь сообщений, а затем обработает сообщение из той же очереди? почему он просто не обрабатывает сообщение напрямую?
Блейк
4
@Blake b / c вы публикуете из одного потока (поток без цикла), но обрабатываете сообщение в другом потоке (поток
цикла
Намного лучше, чем то, что описано на сайте developer.android.com, но было бы неплохо увидеть код предоставленной вами диаграммы.
tfmontague
@numansalati - Обработчик не может отправлять сообщения из цепочки петлителей?
CopsOnRoad 03
78

Начнем с лупера. Вы сможете легче понять взаимосвязь между Looper, Handler и MessageQueue, если поймете, что такое Looper. Также вы можете лучше понять, что такое Looper в контексте графического интерфейса. Looper предназначен для двух вещей.

1) Looper преобразует обычный поток , который завершается, когда его run()метод возвращается, во что-то, что работает непрерывно, пока не будет запущено приложение Android , что необходимо в среде графического интерфейса (технически, он все еще завершается, когда run()метод возвращается. Но позвольте мне пояснить, что я имею в виду, ниже).

2) Looper предоставляет очередь, в которой задачи, которые необходимо выполнить, ставятся в очередь, что также необходимо в среде графического интерфейса.

Как вы, возможно, знаете, когда приложение запускается, система создает поток выполнения для приложения, называемый «основным», и приложения Android обычно выполняются полностью в одном потоке, по умолчанию это «основной поток». Но главный поток - это не какой-то секрет, особый поток . Это просто обычный поток, который вы также можете создать с помощью new Thread()кода, что означает, что он завершается, когда run()возвращается его метод! Подумайте о приведенном ниже примере.

public class HelloRunnable implements Runnable {
    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }
}

Теперь давайте применим этот простой принцип к Android-приложению. Что произойдет, если приложение Android будет запущено в обычном потоке? Поток под названием «основной» или «UI» или что-то еще запускает приложение и рисует весь UI. Итак, пользователям открывается первый экран. И что теперь? Основной поток завершается? Нет, не должно. Он должен подождать, пока пользователи что-то сделают, верно? Но как добиться такого поведения? Что ж, мы можем попробовать с Object.wait()илиThread.sleep(). Например, основной поток завершает свою начальную работу по отображению первого экрана и засыпает. Он просыпается, что означает прерывание, когда выбирается новое задание. Пока все хорошо, но на данный момент нам нужна структура данных, подобная очереди, для хранения нескольких заданий. Подумайте о случае, когда пользователь последовательно касается экрана, и задача занимает больше времени для завершения. Итак, нам нужна структура данных, чтобы удерживать задания, которые должны выполняться в порядке очереди. Кроме того, вы можете себе представить, что реализация потока с постоянным запуском и обработкой задания по прибытии с использованием прерывания непроста и приводит к сложному и часто не поддерживаемому коду. Мы бы предпочли создать новый механизм для этой цели, и в этом вся суть Looper . Официальный документ класса Looperговорит: «По умолчанию потоки не имеют связанного с ними цикла сообщений», а Looper - это класс, «используемый для запуска цикла сообщений для потока». Теперь вы можете понять, что это значит.

Перейдем к Handler и MessageQueue. Во-первых, MessageQueue - это очередь, о которой я упоминал выше. Он находится внутри Looper, вот и все. Вы можете проверить это с помощью исходного кода класса Looper . Класс Looper имеет переменную-член MessageQueue.

Тогда что такое Хэндлер? Если есть очередь, то должен быть метод, который позволит нам поставить новую задачу в очередь, верно? Это то, что делает Хэндлер. Мы можем поставить новую задачу в очередь (MessageQueue), используя различные post(Runnable r)методы. Вот и все. Это все о Looper, Handler и MessageQueue.

Мое последнее слово: в основном Looper - это класс, созданный для решения проблемы, возникающей в среде графического интерфейса. Но такого рода потребности могут возникнуть и в других ситуациях. На самом деле это довольно известный шаблон для многопоточного приложения, и вы можете узнать о нем больше в «Параллельном программировании на Java» Дуга Ли (особенно будет полезна глава 4.1.4 «Рабочие потоки»). Кроме того, вы можете себе представить, что этот вид механизма не является уникальным в структуре Android, но для всех платформ графического интерфейса может потребоваться что-то подобное. Вы можете найти почти такой же механизм в Java Swing framework.

김준호
источник
4
Лучший ответ. Узнал больше из этого подробного объяснения. Интересно, есть ли в блоге более подробное сообщение.
capt.swag
Можно ли добавлять сообщения в MessageQueue без использования Handler?
CopsOnRoad 03
@CopsOnRoad нет, их нельзя добавить напрямую.
Фейсал Насир,
Сделал мой день ... много любви к тебе :)
Рахул Мэтт
26

MessageQueue: Это низкоуровневый класс, содержащий список сообщений, отправляемых a Looper. Сообщения добавляются не напрямую в a MessageQueue, а через Handlerобъекты, связанные с Looper. [ 3 ]

Looper: Он перебирает a, MessageQueueкоторый содержит сообщения для отправки. Фактическая задача управления очередью выполняется агентом, Handlerкоторый отвечает за обработку (добавление, удаление, отправку) сообщений в очереди сообщений. [ 2 ]

Handler: Это позволяет передавать и обрабатывать Messageи Runnableобъекты , связанные с потока MessageQueue. Каждый экземпляр Handler связан с одним потоком и очередью сообщений этого потока. [ 4 ]

Когда вы создаете новый Handler, он привязывается к потоку / очереди сообщений потока, который его создает - с этого момента он будет доставлять сообщения и запускаемые файлы в эту очередь сообщений и выполнять их по мере выхода из очереди сообщений. .

Пожалуйста, просмотрите изображение ниже [ 2 ] для лучшего понимания.

введите описание изображения здесь

AnV
источник
0

Расширяя ответ @K_Anas, с примером, как указано

Широко известно, что в Android незаконно обновлять компоненты пользовательского интерфейса непосредственно из потоков, кроме основного потока.

например, если вы попытаетесь обновить пользовательский интерфейс с помощью Thread.

    int count = 0;
    new Thread(new Runnable(){
        @Override
        public void run() {
            try {
                while(true) {
                    sleep(1000);
                    count++;
                    textView.setText(String.valueOf(count));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


   ).start();

ваше приложение выйдет из строя за исключением.

android.view.ViewRoot $ CalledFromWrongThreadException: только исходный поток, создавший иерархию представлений, может касаться его представлений.

другими словами, вам нужно использовать Handlerкоторый сохраняет ссылку на MainLooper ie Main Threadили UI Threadи передает задачу как Runnable.

  Handler handler = new Handler(getApplicationContext().getMainLooper);
        int count = 0;
        new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    while(true) {
                        sleep(1000);
                        count++;
                        handler.post(new Runnable() {
                           @Override
                           public void run() {
                                 textView.setText(String.valueOf(count));
                           }
                         });

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    ).start() ;
Фейсал Насир
источник