Обработчики, MessageQueue, Looper, все ли они работают в потоке пользовательского интерфейса?

Я пытаюсь разобраться в многопоточности и знаю, что могу использовать Handler для отправки сообщений/запускаемых модулей в MessageQueue, которые, в свою очередь, подхватываются Looper и отправляются обратно в Handler для обработки.

Если я отправляю сообщение в Handler в своей активности, все ли Activity, Handler, MessageQueue и Looper выполняются в потоке пользовательского интерфейса? Если нет, может кто-нибудь объяснить, как все это объединяется? :)


person rogerkk    schedule 04.03.2011    source источник


Ответы (4)


Короткий ответ: все они работают в одном потоке. При создании экземпляра из обратного вызова жизненного цикла Activity все они выполняются в основном потоке пользовательского интерфейса.

Подробный ответ:

Поток может иметь Looper, который содержит MessageQueue. Чтобы использовать эту возможность, вам нужно создать Looper в текущем потоке, вызвав (статический) Looper.prepare(), а затем запустить цикл позвонив (также статический) Looper.loop(). Они являются статическими, потому что предполагается, что в каждом потоке может быть только один Looper.

Вызов loop() обычно не возвращается в течение некоторого времени, но продолжает принимать сообщения ("задачи", "команды" или как бы вы их ни называли) из MessageQueue и обрабатывает их по отдельности (например, обратный вызов Runnable, содержащегося в сообщении). Когда в очереди не осталось сообщений, поток блокируется до тех пор, пока не появятся новые сообщения. Чтобы остановить Looper, вы должны вызвать для него quit() (что, вероятно, не останавливает цикл немедленно, а скорее устанавливает частный флаг, который периодически проверяется из цикла, сигнализируя об остановке).

Однако вы не можете добавлять сообщения в очередь напрямую. Вместо этого вы регистрируете MessageQueue.IdleHandler для ожидания обратного вызова queueIdle(), в котором вы можете решить, хотите ли вы чего-то или нет. Все обработчики вызываются по очереди. (Поэтому «очередь» на самом деле не очередь, а набор обратных вызовов, которые нужно вызывать регулярно.)

Примечание к предыдущему абзацу: я действительно догадался. Я не смог найти никакой документации по этому поводу, но это имело бы смысл.

обновление: см. ahcox и его ответ.

Поскольку это требует много работы, платформа предоставляет класс Handler для упрощения. Когда вы создаете экземпляр Handler, он (по умолчанию) привязывается к Looper, уже присоединенному к текущему потоку. (Handler знает, к какому Looper присоединить, потому что мы вызывали prepare() ранее, что, вероятно, сохранило ссылку на Looper в ThreadLocal.)

С Handler вы можете просто вызвать post(), чтобы "поместить сообщение в очередь сообщений потока" (так сказать). Handler позаботится обо всех обратных вызовах IdleHandler и проследит за тем, чтобы отправленный вами Runnable был выполнен. (Он также может проверить, правильное ли время, если вы отправили сообщение с задержкой.)

Просто для ясности: единственный способ заставить циклический поток делать что-то — отправить сообщение в его цикл. Это действует до тех пор, пока вы не вызовете quit() в циклере.


Относительно потока пользовательского интерфейса Android: в какой-то момент (вероятно, до того, как будут созданы какие-либо действия и т.п.) фреймворк настроил Looper (содержащий MessageQueue) и запустил его. С этого момента все, что происходит в потоке пользовательского интерфейса, проходит через этот цикл. Это включает в себя управление жизненным циклом деятельности и так далее. Все обратные вызовы, которые вы переопределяете (onCreate(), onDestroy()...), по крайней мере косвенно отправляются из этого цикла. Вы можете увидеть это, например, в трассировке стека исключения. (Можете попробовать, просто напишите int a = 1 / 0; где-нибудь в onCreate()...)


Я надеюсь в этом есть смысл. Извините за неясность ранее.

person user634618    schedule 04.03.2011
comment
Но в методе Looper.loop() настроен бесконечный цикл, в котором сообщения выбираются из MessageQueue, почему это не блокирует все остальное, происходящее в потоке пользовательского интерфейса? - person rogerkk; 04.03.2011
comment
Прошу прощения за расплывчатость вопросов, но я все еще немного запутался. Я получаю часть о подключении к существующему луперу. Но этот существующий лупер также будет содержать бесконечный цикл, о котором я говорю. Я не понимаю, как это может работать в любом потоке, не блокируя все остальное, происходящее в том же потоке? - person rogerkk; 07.03.2011
comment
Отлично, большое спасибо, что прояснили это! Последний раздел о потоке пользовательского интерфейса подтвердил мое скрытое подозрение. - person rogerkk; 10.03.2011
comment
Кстати. вместо того, чтобы вызывать исключение, вы можете просто бросить его. бросить новое исключение RuntimeException (FooBar); Однако вы не можете сделать это в середине блока кода, потому что компилятор будет жаловаться, что остальная часть блока никогда не будет исполняемой. Однако вы можете обмануть его, сделав это следующим образом: if (true) {throw new RuntimeException(Got ya!);} Мне нравится использовать это самому, поскольку ваши намерения более ясны, чем что-то вроде намеренной арифметической ошибки. - person Timo; 25.10.2011
comment
Отличный ответ. Спасибо за объяснение. - person Craig B; 29.01.2012
comment
Это очень полезный ответ. Просто чтобы прояснить вашу неуверенность, в MessageQueue есть настоящая очередь. Message содержит ссылку на Message, которую MessageQueue использует для формирования навязчивой очереди связанных списков. Вы можете видеть, что сообщения захватываются спереди в MessageQueue.pullNextLocked() (непубличный метод). Только если эта очередь сообщений пуста, вызываются обработчики бездействия. Последним средством является ожидание потоком следующего отложенного крайнего срока Message или появления нового Message. - person ahcox; 09.03.2012
comment
@rogerkk: Что касается блокировки потока, о которой вы упоминаете, вы правы, он бесконечно зацикливается, подбирая следующее сообщение или исполняемый файл, который приходит. И он не будет блокироваться, а займет столько времени, сколько потребуется для обработки этого сообщения или работоспособного, опубликованного из другого потока. Именно поэтому нежелательно выполнять длительные операции в потоке пользовательского интерфейса, так как система будет жаловаться на ANR, если вы это сделаете. Но запустить цикл в потоке, отличном от пользовательского интерфейса, можно — это просто непрерывный цикл обработки сообщений, обрабатывающий каждое сообщение одно за другим. - person Phillip; 10.03.2012
comment
Можно ли узнать, сколько сообщений осталось в очереди? Например, в функции handleMessage() я хотел бы показать в логарифме обработку Message x of y для отладки. Или Осталось y сообщений для обработки - person Someone Somewhere; 14.09.2012

Следуя части вопроса «как все это объединяется». Как писал пользователь 634618, циклогенератор берет на себя поток, основной поток пользовательского интерфейса в случае основного Looper приложения.

  • Looper.loop() извлекает сообщения из своей очереди сообщений. Каждое сообщение имеет ссылку на связанный обработчик, которому оно должно быть возвращено (целевой элемент).
  • Inside Looper.loop() for each message gotten from the queue:
    • loop() calls public void Handler.dispatchMessage(Message msg) using the Handler that is stored in the Message as its target member.
    • Если в сообщении есть элемент обратного вызова Runnable, он запускается.
    • В противном случае, если у обработчика установлен общий обратный вызов, он запускается.
    • В противном случае handleMessage() обработчика вызывается с сообщением в качестве аргумента. (Обратите внимание: если вы создадите подкласс Handler, как это делает AsyncTask, вы можете переопределить handleMessage(), как это делается.)

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

Когда мы запускаем некоторый код в другом потоке и хотим отправить Runnable для выполнения в потоке пользовательского интерфейса, мы можем сделать это следующим образом:

// h is a Handler that we constructed on the UI thread.
public void run_on_ui_thread(final Handler h, final Runnable r)
{
   // Associate a Message with our Handler and set the Message's
   // callback member to our Runnable:
   final Message message = Message.obtain(h, r);

   // The target is the Handler, so this asks our Handler to put
   // the Message in its message queue, which is the exact same
   // message queue associated with the Looper on the thread on
   // which the Handler was created:
   message.sendToTarget();
}
person ahcox    schedule 09.03.2012
comment
Отличное понимание! Спасибо. - person Yogesh Umesh Vaity; 30.10.2017

Я пытаюсь реализовать этот интерфейс самостоятельно, чтобы понять концепцию. По простоте, просто используйте интерфейс по необходимости. Вот мой тестовый код:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class TestLooper {

    public static void main(String[] args) {
        UIThread thread = new UIThread();
        thread.start();

        Handler mHandler = new Handler(thread.looper);
        new WorkThread(mHandler, "out thread").run();
    }
}

class Looper {
    private BlockingQueue<Message> message_list = new LinkedBlockingQueue<Message>();

    public void loop() {

        try {
            while (!Thread.interrupted()) {
                Message m = message_list.take();
                m.exeute();
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public void insertMessage(Message msg) {
        message_list.add(msg);
    }

}

class Message {
    String data;
    Handler handler;

    public Message(Handler handler) {
        this.handler = handler;
    }

    public void setData(String data) {
        this.data = data;
    }

    public void exeute() {
        handler.handleMessage(this);
    }
}

class Handler {

    Looper looper;

    public Handler(Looper looper) {
        this.looper = looper;
    }

    public void dispatchMessage(Message msg) {
        System.out.println("Handler dispatchMessage" + Thread.currentThread());
        looper.insertMessage(msg);
    }

    public Message obtainMessage() {
        return new Message(this);
    }

    public void handleMessage(Message m) {
        System.out.println("handleMessage:" + m.data + Thread.currentThread());
    }
}

class WorkThread extends Thread {
    Handler handler;
    String tag;

    public WorkThread(Handler handler, String tag) {
        this.handler = handler;
        this.tag = tag;
    }

    public void run() {
        System.out.println("WorkThread run" + Thread.currentThread());
        Message m = handler.obtainMessage();
        m.setData("message " + tag);
        handler.dispatchMessage(m);
    }
}

class UIThread extends Thread {

    public Looper looper = new Looper();

    public void run() {

            //create handler in ui thread
        Handler mHandler = new Handler(looper);

        new WorkThread(mHandler, "inter thread").run();
        System.out.println("thead run" + Thread.currentThread());
        looper.loop();
    }

}
person julianjing    schedule 26.06.2013

Если я отправлю сообщение обработчику в своей деятельности, будут ли Activity, Handler, MessageQueue и Looper работать в потоке пользовательского интерфейса? Если нет, может кто-нибудь объяснить, как все это объединяется? :)

Это зависит от того, как вы создаете обработчик.

Дело 1:

Handler()

Конструктор по умолчанию связывает этот обработчик с Looper для текущего потока.

Если вы создадите Handler таким образом в потоке пользовательского интерфейса, Handler будет связано с Looper потока пользовательского интерфейса. MessageQueue также связан с Looper с потоком пользовательского интерфейса.

Случай 2:

Handler (Looper looper)

Используйте предоставленный Looper вместо стандартного.

Если я создам HandlerThread и передам Looper HandlerThread в Handler, Handler и Looper связаны с HandlerThread, а не с потоком пользовательского интерфейса. Handler, MessageQueue и Looper связаны с HandlerThread.

Вариант использования: вы хотите выполнить операцию Network OR IO. Вы не можете выполнить его в потоке пользовательского интерфейса, поэтому HandlerThread вам удобен.

 HandlerThread handlerThread = new HandlerThread("NetworkOperation");
 handlerThread.start();
 Handler requestHandler = new Handler(handlerThread.getLooper());

Если вы хотите передать данные обратно из HandlerThread в поток пользовательского интерфейса, вы можете создать еще один обработчик (например, responseHandler) с Looper из потока пользовательского интерфейса и вызвать sendMessage. Поток пользовательского интерфейса responseHandler должен переопределить handleMessage

Обратитесь к этим сообщениям для более подробной информации.

Какова цель Looper и как его использовать? ( Для понятий )

Android: тост в потоке (например, код, связывающий все эти концепции)

person Ravindra babu    schedule 31.08.2017