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

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

5 ответов

Решение

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

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

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

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

Помимо документации по классу, здесь все обсуждается.

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

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

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

Как вы, возможно, знаете, когда приложение запускается, система создает для приложения поток выполнения, называемый "основным", и приложения 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 будет запущено в обычном потоке? Поток с именем "main" или "UI" или что-то еще запускает приложение и рисует весь UI. Итак, первый экран отображается для пользователей. И что теперь? Основной поток заканчивается? Нет, не должно. Следует подождать, пока пользователи что-то сделают, верно? Но как мы можем достичь этого поведения? Ну, мы можем попробовать с Object.wait() или же Thread.sleep(), Например, основной поток завершает свою начальную работу для отображения первого экрана и спит. Он пробуждается, что означает прерванный, когда выбирается новая работа. Пока все хорошо, но в данный момент нам нужна структура данных в виде очереди для хранения нескольких заданий. Подумайте о случае, когда пользователь касается экрана последовательно, и выполнение задачи занимает больше времени. Таким образом, нам нужна структура данных для хранения заданий, выполняемых в порядке "первым пришел - первым вышел". Кроме того, вы можете себе представить, что реализовать поток, который постоянно выполняется и обрабатывает задание, когда поступает, используя прерывание, нелегко и приводит к сложному и часто не поддерживаемому коду. Мы бы предпочли создать новый механизм для этой цели, и это то, что Лупер это все. Официальный документ класса Looper гласит: "С потоками по умолчанию не связан цикл сообщений", а Looper - это класс, "используемый для запуска цикла сообщений для потока". Теперь вы можете понять, что это значит.

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

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

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

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

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

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

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

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

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

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

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

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

MessageQueue, Обработчик, Looper в AndroidMessageQueue:

MessageQueue - это цикл сообщений или очередь сообщений, которая в основном содержит список сообщений или Runnables (набор исполняемого кода).

Другими словами, MessageQueue - это очередь, в которой есть задачи, называемые сообщениями, которые должны быть обработаны.

Примечание. Android поддерживает MessageQueue в основном потоке.

Looper:

Looper - это работник, который обслуживает MessageQueue для текущего потока. Looper проходит по очереди сообщений и отправляет сообщения соответствующим обработчикам для обработки.

Любой поток может иметь только один уникальный Looper, это ограничение достигается с помощью концепции хранилища ThreadLocal.

Преимущества Looper и MessageQueue

Есть некоторые преимущества использования Looper и MessageQueue, как описано ниже:

· С использованием MessageQueue выполнение является последовательным, поэтому в случае параллельных потоков это позволит избежать условий гонки.

· Обычно поток не может быть повторно использован после завершения его работы. Но поток с Looper сохраняется до тех пор, пока вы не вызовете метод quit, поэтому вам не нужно создавать новый экземпляр каждый раз, когда вы хотите запустить задание в фоновом режиме.

Обработчик:

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

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

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

Существует два основных варианта использования обработчика:

(1) Чтобы запланировать выполнение сообщений и исполняемых файлов как определенный момент в будущем. Другими словами, выполните действия в том же потоке в будущем.

(2) поставить в очередь действие, которое будет выполнено в другом потоке, чем ваш собственный. Другими словами, поставьте в очередь действие для выполнения в другом потоке.

Расширяя ответ @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 т.е. 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() ;
Другие вопросы по тегам