Какая связь между 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 - это цикл сообщений или очередь сообщений, которая в основном содержит список сообщений или 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() ;