Обработчики, MessageQueue, Looper, все они работают в потоке пользовательского интерфейса? - PullRequest
38 голосов
/ 04 марта 2011

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

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

Ответы [ 4 ]

65 голосов
/ 04 марта 2011

Краткий ответ: они все работают в одном потоке.При создании экземпляра из обратного вызова жизненного цикла 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 выполнен.(Он также может проверить, подходит ли уже время, если вы отправили сообщение с задержкой.)

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


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


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

11 голосов
/ 09 марта 2012

В продолжение вопроса «как все это получается».Как пишет user634618, петлитель захватывает поток, основной поток пользовательского интерфейса в случае основного Looper.

  • Looper.loop() приложения, который извлекает сообщения из своей очереди сообщений.Каждое сообщение имеет ссылку на связанный обработчик, которому оно должно быть возвращено (целевой член).
  • Внутри Looper.loop() для каждого сообщения, полученного из очереди:
    • loop()вызывает public void Handler.dispatchMessage(Message msg), используя обработчик, который хранится в сообщении в качестве целевого элемента.
    • Если сообщение содержит элемент обратного вызова 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();
}
2 голосов
/ 26 июня 2013

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

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();
    }

}
1 голос
/ 31 августа 2017

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

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

Случай 1:

Handler()

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

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

Случай 2:

Handler (Looper looper)

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

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

Вариант использования: вы хотите выполнить операцию сетевого ИЛИ ввода-вывода.Вы не можете выполнить его в потоке пользовательского интерфейса, и, следовательно, HandlerThread вам пригодится.

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

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

Подробнее об этом см. В этих сообщениях.

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

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

...