41

我正在尝试围绕线程进行处理,并且我知道我可以使用 aHandler将消息/可运行文件发布到MessageQueue,而后者又被 拾取Looper并发送回Handler处理。

如果我Handler在我的活动中发布到 a ,那么、Activity和都在 UI 线程上运行吗?如果没有,有人可以解释一下这一切是如何结合在一起的吗?:)HandlerMessageQueueLooper

4

4 回答 4

70

简短的回答:它们都在同一个线程上运行。如果从生命周期回调实例化Activity,它们都在主 UI 线程上运行。

长答案:

一个线程可能有一个Looper,其中包含一个MessageQueue为了使用此功能,您必须Looper通过调用 (the static) 在当前线程上创建一个Looper.prepare(),然后通过调用 (the also static) 来启动循环Looper.loop()Looper这些是静态的,因为每个线程只应该有一个。

调用loop()通常在一段时间内不会返回MessageQueue,但会不断从 中获取消息(“任务”、“命令”或任何您喜欢的名称)并单独处理它们(例如,通过回调Runnable包含在消息中的 a )。当队列中没有消息时,线程会阻塞,直到有新消息。要停止 a Looper,您必须调用quit()它(它可能不会立即停止循环,而是设置一个从循环中定期检查的私有标志,指示它停止)。

但是,您不能直接将消息添加到队列中。相反,您注册 aMessageQueue.IdleHandler以等待queueIdle()回调,您可以在其中决定是否要执行某些操作。依次调用所有处理程序。(所以“队列”并不是真正的队列,而是定期调用的回调集合。)

关于上一段的注意事项:我实际上猜到了。我找不到任何关于此的文档,但这是有道理的。

更新:ahcox的评论他的回答

因为这是很多工作,所以框架提供了Handler类来简化事情。创建Handler实例时,它(默认情况下)绑定到Looper已附加到当前线程的实例。(theHandler知道Looper要附加什么,因为我们之前调用prepare()过,它可能存储了对 a 的引用LooperThreadLocal

使用 a Handler,您只需调用post()“将消息放入线程的消息队列”(可以这么说)。将Handler处理所有IdleHandler回调内容并确保您发布Runnable的内容被执行。(如果您延迟发布,它也可能会检查时间是否正确。)

只是要明确一点:真正使循环线程做某事的唯一方法将消息发布到它的循环中。这在您调用 looper 上的 quit() 之前是有效的。


关于 android UI 线程:在某些时候(可能在创建任何活动等之前),框架已设置 a Looper(包含 a MessageQueue)并启动它。从这一点开始,UI 线程上发生的一切都通过该循环。这包括活动生命周期管理等。您覆盖的所有回调 ( onCreate(), onDestroy()...) 至少是从该循环中间接分派的。例如,您可以在异常的堆栈跟踪中看到这一点。(你可以试试,写int a = 1 / 0;onCreate()...的某个地方)


我希望这是有道理的。抱歉之前不清楚。

于 2011-03-04T12:55:43.947 回答
11

跟进问题的“这一切是如何结合在一起的”部分。正如 user634618 所写,looper 接管了一个线程,即应用程序 main 的主 UI 线程Looper

  • Looper.loop()将消息从其消息队列中拉出。每个 Message 都有一个对关联的 Handler 的引用,它将被返回给(目标成员)。
  • Looper.loop()从队列中获取的每条消息的 内部:
    • loop()public void Handler.dispatchMessage(Message msg)使用存储在 Message 中的 Handler 作为其目标成员进行调用。
    • 如果消息具有 Runnable 回调成员,则运行。
    • 否则,如果 Handler 有一个共享的回调集,则运行。
    • 否则,handleMessage()使用消息作为参数调用处理程序。(注意,如果你像 AsyncTask 那样子类化 Handler ,你可以handleMessage()像它一样覆盖。)

关于所有协作对象都在同一个 UI 线程上的问题,Handler必须在与Looper它将发送消息的线程相同的线程上创建。它的构造函数将查找 currentLooper并将其存储为成员,将 绑定Handler到 that Looper。它还将Looper直接在其自己的成员中引用该消息队列。可Handler用于Looper从任何线程向 发送工作,但消息队列的此标识路由要在Looper的线程上完成的工作。

当我们在另一个线程上运行一些代码并希望发送一个 Runnable 以在 UI 线程上执行时,我们可以这样做:

// 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();
}
于 2012-03-09T18:04:39.280 回答
2

为了理解这个概念,我尝试自己实现这些接口。为了简单起见,只需根据需要使用接口。这是我的测试代码:

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

}
于 2013-06-26T08:56:02.793 回答
2

如果我在我的活动中发布到处理程序,活动、处理程序、消息队列和循环器是否都在 UI 线程上运行?如果没有,有人可以解释一下这一切是如何结合在一起的吗?:)

这取决于您如何创建Handler

情况1:

Handler()

默认构造函数将此处理程序与当前线程的Looper相关联。

如果Handler在 UI 线程中这样创建,Handler则与LooperUI 线程相关联。MessageQueueLooper与 UI Thread 相关联。

案例二:

Handler (Looper looper)

使用提供的 Looper 而不是默认的 Looper。

如果我创建一个HandlerThread并将 HandlerThread 的 Looper 传递给 Handler,则 Handler 和 Looper 与 HandlerThread 相关联,而不是 UI Thread。Handler,MessageQueue并与Looper相关联HandlerThread

用例:您想要执行 Network OR IO 操作。您不能在 UI 线程上执行它,因此HandlerThread对您来说很方便。

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

如果您想将数据从 HandlerThread 传递回 UI Thread,您可以使用Looperfrom UI Thread 再创建一个 Handler(比如 responseHandler )并调用sendMessage. UI 线程responseHandler应该覆盖handleMessage

有关更多详细信息,请参阅这些帖子。

Looper 的目的是什么以及如何使用它? (对于概念)

Android:Toast in a thread (例如通过链接所有这些概念的代码)

于 2017-08-31T14:14:45.660 回答