Android 消息机制:Handler、MessageQueue 和 Looper

Android并发编程之Handler,MessageQueue,Looper详解 Handler和Looper,MessageQueue之间是什么关系? Looper和MessageQueue是线程中的概念,但是线程默认是没有Looper和MessageQueue的,我们需要手动去设置他们,当一个线程有了Looper和MessageQueue后,就可以关联一个Handler,我们再通过这个Handler,就可以从别的线程中发送消息给这个线程来执行。 我们给一个线程配置了Lo 阅读详情

在这篇文章中,我们将会讨论 Android 的消息机制。提到 Handler,有过一些 Android 开发经验的都应该很清楚它的作用,通常我们使用它来通知主线程更新 UI。但是 Handler 需要底层的 MessageQueue 和 Looper 来支持才能运作。这篇文章中,我们将会讨论它们三个之间的关系以及实现原理。

在这篇文章中,因为涉及线程方面的东西,所以就避不开 ThreadLocal。笔者在之前的文章中有分析过该 API 的作用,你可以参考笔者的这篇文章来学习下它的作用和原理,本文中我们就不再专门讲解:《Java 并发编程:ThreadLocal 的使用及其源码实现》。

1、Handler 的作用

通常,当我们在非主线程当中做了异步的操作之后使用 Handler 来在主线程当中更新 UI。之所以这么设计无非就是因为 Android 中的 View 不是线程安全的。之所以将 View 设计成非线程安全的,是因为:1).对 View 进行加锁之后会增加控件使用的复杂度;2).加锁之后会降低控件执行的效率。但 Handler 并非只能用来在主线程当中更新 UI,确切来说它有两个作用:

  1. 任务调度:即通过 post() 和 send() 等方法来指定某个任务在某个时间执行;
  2. 线程切换:你也许用过 RxJava,但如果在 Android 中使用的话还要配合 RxAndroid,而这里的 RxAndroid 内部就使用 Handler 来实现线程切换。

下文中,我们就来分别看一下它的这两个功能的作用和原理。

1.1 任务调度

使用 Hanlder 可以让一个任务在某个时间点执行或者等待某段时间之后执行。Handler 为此提供了许多方法,从方法的命名上,我们可以将其分成 post() 和 sned() 两类方法。post() 类的用来指定某个 Runnable 在某个时间点执行,send() 类的用来指定某个 Message 在某个时间点执行。

这里的 Message 是 Android 中定义的一个类。它内部有多个字段,比如 what、arg1、arg2、replyTo 和 sendingUid 来帮助我们指定该消息的内容和对象。同时, Message 还实现了 Parcelable 接口,这表明它可以被用来跨进程传输。此外,它内部还定义了一个 Message 类型的 next 字段,这表明 Message 可以被用作链表的结点。实际上 MessageQueue 里面只存放了一个 mMessage,即链表的头结点。所以,MessageQueue 内部的消息队列,本质上是一个单链表,每个链表的结点就是 Message。

当调用 post() 类型的方法来调度某个 Runnable 的时候,首先会将其包装成一个 Message,然后再使用 send() 类的方法进行任务分发。所以,不论是 post() 类的方法还是 send() 类的方法,最终都会使用 Handler 的 sendMessageAtTime() 方法来将其加入到队列中:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        // ... 无关代码
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

使用 Handler 进行任务调度是非常简单的。下面的代码就实现了让一个 Runnable 在 500ms 之后执行的逻辑:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        // do something
    }
}, 500);

上面的任务执行方式在主线程中执行不会出现任何问题,如果你在非主线程中执行的话就可能会出现异常。原因我们后面会讲解。

既然每个 Runnable 被 post() 发送之后还要被包装成 Message,那么 Message 的意义何在呢?

Runnable 被包装的过程依赖于 Handler 内部的 getPostMessage() 方法。下面是该方法的定义:

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

可见,我们的 Runnable 会被赋值给 Message 的 callback。这种类型的消息无法做更详细的处理。就是说,我们无法利用消息的 what、arg1 等字段(本身我们也没有设置这些字段)。如果我们希望使用 Message 的这些字段信息,就需要:

  1. 首先,要使用 send() 类型的方法来传递我们的 Message 给 Handler;
  2. 然后,我们的 Handler 要覆写 handleMessage() 方法,并在该方法中获取每个 Message 并根据其内部的信息依次处理。

下面的一个例子用来演示 send() 类型的方法。首先,我们要定义 Handler 并覆写其 handleMessage() 方法来处理消息:

private final static int SAY_HELLO = 1;

private static Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case SAY_HELLO:
                LogUtils.d("Hello!");
                break;
        }
    }
};

然后,我们向该 Handler 发送消息:

Message message = Message.obtain(handler);
message.what = SAY_HELLO;
message.sendToTarget();

这样,我们的 Handler 接收到了消息并根据其 what 得知要 SAY_HELLO,于是就打印出了日志信息。除了调用 Message 的 sendToTarget() 方法,我们还可以直接调用 handler 的 sendMessage() 方法(sendToTarget() 内部调用了 handler 的 sendMessage())。

1.2 线程切换

下面我们用了一份示例代码,它会先在主线程当中实例化一个 Handler,然后在某个方法中,我们开启了一个线程,并执行了某个任务。2 秒之后任务结束,我们来更新 UI。

// 在主线程中获取 Handler
private static Handler handler = new Handler();		

// 更新UI,会将消息发送到主线程当中
new Thread(() -> {
    try {
        Thread.sleep(2000);
        handler.post(() -> getBinding().tv.setText("主线程更新UI"));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

上面之所以能够在主线程当中更新 UI,主要是因为我们的 Handler 是在主线程当中进行获取的。随后,我们调用 handler 的 post() 方法之后,传入的 Runnable 会被包装成 Message,然后加入到主线程对应的消息队列中去,并由主线程对应的 Looper 获取到并执行。所以,就使得该 Runnable 的操作最终在主线程中完成。

也许你会觉得先在主线程当中获取到 Handler 然后再使用比较麻烦。别担心,我们还有另一种方式来解决这个问题。我们可以直接使用 Looper 的 getMainLooper() 方法来获取主线程对应的 Looper,然后使用它来实例化一个 Handler 并使用该 Handler 来处理消息:

new Handler(Looper.getMainLooper())
    .post(() -> getBinding().tv.setText("主线程更新UI"));

本质上,当我们调用 Handler 的无参构造方法,或者说不指定 Looper 的构造方法的时候,会直接使用当前线程对应的 Looper 来实例化 Handler。每个线程对应的 Looper 存储在该线程的局部变量 ThreadLocal 里。当某个线程的局部变量里面没有 Looper 的时候就会抛出一个异常。所以,我们之前说直接使用 new 来实例化一个 Handler 的时候可能出错就是这个原因。

主线程对应的 Looper 会在 ActivityThread 的静态方法 main() 中被创建,它会调用 Looper 的 prepareMainLooper() 静态方法来创建主线程对应的 Looper。然后会调用 Looper 的 loop() 静态方法来开启 Looper 循环以不断处理消息。这里的 ActivityThread 用来处理应用进程中的活动和广播的请求,会在应用启动的时候调用。ActivityThread 内部定义了一个内部类 H,它继承自 Handler,同样运行在主线程中,用来处理接收到的来自各个活动、广播和服务的请求。

除了使用主线程对应的 Looper,我们也可以开启我们自定义线程的 Looper。下面的代码中,我们开启了一个线程,并在线程中先调用 Looper 的 prepare() 静态方法,此时 Looper 会为我们当前的线程创建 Looper,然后将其加入到当前线程的局部变量里面。随后,当我们调用 Looper 的 loop() 方法的时候就开启了 Looper 循环来不断处理消息:

new Thread(() -> {
    LogUtils.d("+++++++++" + Thread.currentThread());
    Looper.prepare();
    new Handler().post(() -> LogUtils.d("+++++++++" + Thread.currentThread()));
    Looper.loop();
}).start();

从以上的内容我们可以看出,Handler 之所以能够实现线程切换,主要的原因是其内部的消息队列是对应于每一个线程的。发送的任务会在该线程对应的消息队列中被执行。而成功获取到该线程对应的消息队列就依靠 ThreadLocal 来对每个线程对应的消息队列进行存储。

2、源码解析

以上,我们分析了 Handler 的主要的两种主要用途,并且在这个过程中,我们提及了许多 Handler、MessageQueue 和 Looper 的底层设计。在上面的文章中,我们只是使用了文字来进行描述。在下文中,我们来通过源码来验证我们上面提到的一些内容。

2.1 实例化 Handler

Handler 了提供了多个重载的构造方法,我们可以将其分成两种主要的类型。一种在构造方法中需要明确指定一个 Looper,另一种在构造方法中不需要指定任何 Looper,在构造方法内部会获取当前线程对应的 Looper 来初始化 Handler。

第一种初始化的方式最终都会调用下面的方法来完成初始化。这个方法比较简单,是基本的赋值操作:

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

第二种初始化的方式最终会调用下面的方法。这里使用 Looper 的静态方法 myLooper() 来获取当前线程对应的 Looper。如果当前线程不存在任何 Looper 就会抛出一个异常。

public Handler(Callback callback, boolean async) {
    // 潜在内存泄漏的检查
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    // 使用 Looper 的静态方法 myLooper() 来获取当前线程的 Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException();
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

而 Looper 的静态方法 myLooper() 会使用线程局部变量 sThreadLocal 来获取之前存储到该线程内部的 Looper:

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

2.2 Looper 的初始化

前面我们也说过 Looper 的创建过程。对于主线程的 Looper 会在 ActivityThread 的 main() 方法中被调用:

public static void main(String[] args) {
    // ... 无关代码
    Looper.prepareMainLooper();
    // ... 无关代码
    // 开启 Looper 循环
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

这里调用了 Looper 的静态方法 prepareMainLooper() 来初始化主线程的 Looper:

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

其内部先调用了 prepare(boolean) 方法来初始化一个 Looper 并将其放在线程局部变量 sThreadLocal 中,然后判断 sMainLooper 是否之前存在过。这是一种基本的单例校验,显然,我们只允许主线程的 Looper 被实例化一次。

同样,非主线程的 Looper 也只允许被实例化一次。当我们在非主线程实例化一个 Looper 的时候会调用它的 prepare() 静态方法。它同样调用了 prepare(boolean) 方法来初始化一个 Looper 并将其放在线程局部变量 sThreadLocal 中。所以,主线程和非主线程的 Looper 实例化的时候本质上是调用同样的方法,只是它们实现的时机不同,并且,都只能被实例化一次。

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

经过上述分析,我们可以得知,对于一个线程只能实例化一个 Looper,所以当我们在同一个线程中多次创建 Handler 实例,它们是共享一个 Looper 的。或者说是一个 Looper 对应多个 Handler 也是可以的。

2.3 MessageQueue 的实例化

相比于 Looper 和 Handler,MessageQueue 就显得相对复杂一些。因为内部用到了 JNI 编程。初始化、销毁和入队等事件都用到了 native 的方法。你可以在 android_os_MessageQueue 查看其源码的定义。

每当我们实例化一个 Looper 的时候会调用它的构造方法,并在其中实例化一个 MessageQueue:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

在实例化 Handler 的小节中可以看出,每次实例化一个 Handler 的时候,会从当前线程对应的 Looper 中取出 MessageQueue。所以,这里我们又可以得出结论一个 Handler 对应一个 MessageQueue。

当我们实例化一个 MessageQueue 的时候会使用它的构造方法。这里会调用 native 层的 nativeInit() 方法来完成 MessageQueue 的初始化:

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

在 native 层,nativeInit() 方法的定义如下:

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }
    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

从上面我们可以看出,在该方法中实例化了一个 NativeMessageQueue 之后返回了 mPtr 作为是 Java 层 MessageQueue 与NativeMessesageQueue 的桥梁。这个 long 类型的成员保存了 native 实例,这是 jni 开发中常用到的方式。因此 MessageQueue 同样使用 mPtr 来表示 native 层的消息队列。NativeMessageQueue 在 native 层的部分定义和其构造方法的定义如下。

class NativeMessageQueue : public MessageQueue, public LooperCallback {
// ... 无关代码
NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

从上面我们可以看出,NativeMessageQueue 继承自 MessageQueue。并且在其内部实例化了一个 native 层的 Looper(其源码在 Looper)。

在 Android 的 native 层存在着一个于 Java 层类似的 Looper,它的主要作用是用来与 Java 层的 Looper 相互配合完成 Android 中最主要的线程通信。当消息队列中有消息存入时,会唤醒 Natvice 层的 Looper。当消息队列中没有消息时或者消息尚未到处理时间时, Natvice 层的 Looper 会 block 住整个线程。所以,创建了 Java Looper 的线程只有在有消息待处理时才处于活跃状态,无消息时 block 在等待消息写入的状态。既然如此,当我们在主线程中开启了 Looper 循环的话,为什么不会 block 住整个线程而导致 ANR 呢?这是因为,我们的主线程的消息都会发送给主线程对应的 Looper 来处理,所以,本质上,我们主线程中的许多事件也都是以消息的形式发送给主线程的 Handler 来进行处理的。只有当某个消息被执行的时间过长的时候才会出现 ANR。

上面我们实例化了一个 Native 层的 Looper。在其中主要做到的逻辑如下:

void Looper::rebuildEpollLocked() {
    // 如果之前存在的话就关闭之前的 epoll 实例
    if (mEpollFd >= 0) {
        mEpollFd.reset(); // 关闭旧的epoll实例
    }
    // 申请新的 epoll 实例,并且注册 “Wake管道”
    mEpollFd.reset(epoll_create(EPOLL_SIZE_HINT));
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
    struct epoll_event eventItem;
    // 把未使用的数据区域进行置0操作
    memset(& eventItem, 0, sizeof(epoll_event));
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd.get();
    // 将唤醒事件 (mWakeEventFd) 添加到 epoll 实例 (mEpollFd)
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s", strerror(errno));
    // 这里主要添加的是Input事件如键盘,传感器输入,这里基本上由系统负责,很少主动去添加
    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);
        // 将 request 队列的事件,分别添加到 epoll 实例
        int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
    }
}

这里涉及了 epoll 相关的知识。epoll 是一个可扩展的 Linux I/O 事件通知机制,用来实现多路复用 (Multiplexing)。它将唤醒事件按对应的 fd 注册进 epoll,然后 epoll 帮你监听哪些唤醒事件上有消息到达。此时的唤醒事件应该采用非阻塞模式。这样,整个过程只在调用 epoll 的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的响应模式。

上面的代码中使用了 epoll_ctl 方法来将被监听的描述符添加到 epoll 句柄。关于 epoll 的指令,可以参考这篇博文 《epoll机制:epoll_create、epoll_ctl、epoll_wait、close》。这部分代码的主要作用是创建一个 epoll 实例并用它来监听 event 触发。

2.4 消息的执行过程

2.4.1 消息入队的过程

在介绍 Handler 的使用的时候,我们也说过不论是 Runnable 还是 Message 最终都会被封装成 Meseage 并加入到队列中。那么,加入队列之后又是怎么被执行的呢?

首先,我们先看下入队的过程。以下是 Handler 中定义的方法,每当我们将一个消息入队的时候,都会调用它来完成。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

从上面可以看出,入队的时候实际上是使用了 MessageQueue 的 enqueueMessage() 方法。所以,我们再来看下该方法的定义:

boolean enqueueMessage(Message msg, long when) {
    // ... 无关代码,校验
    synchronized (this) {
        // ... 无关代码
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p;
            prev.next = msg;
        }

        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

从上面的方法可以看出,所谓的入队操作本质上就是一个将新的消息加入到队列中的逻辑。当然,这里加入的时候要根据消息的触发时间对消息进行排序。然后,会根据 needWake 来决定是否调用 native 层的方法进行唤醒。只有当当前的头结点消息之前存在栅栏 (barrier) 并且新插入的消息是最先要被触发的异步消息就进行唤醒。当一般情况下是无需进行唤醒的。

这里的 nativeWake() 方法会最终调用 native 层的 Looper 的 awake() 方法:

void Looper::wake() {
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s", mWakeEventFd.get(), strerror(errno));
        }
    }
}

此方法向 mWakeEventFd 写入了一个字节的内容。到底是什么内容并不重要,重要的是 fd 存在内容了,换句话说就是 mWakeEventFd 可读了,也就是 Native 层的 Looper 的线程从 block 状态中醒了过来。之所以需要进行唤醒,是因为,每次我们处理了消息之后会根据下个消息执行的时间进行唤醒。如果新插入的消息是最新的消息,那么显然,我们需要把唤醒的时间重置。(Native 层的 Looper 会在我们调用 Java 层的 MessageQueue 的时候执行 epoll_wait 时进入 block 状态。)

2.4.2 消息执行的过程

在上文中,我们分析了 MessageQueue 将消息入队的过程。那么这些消息要在什么时候被执行呢?在介绍 Handler 的使用的时候,我们也提到过当我们实例化了 Looper 之后都应该调用它的 loop() 静态方法来处理消息。下面我们来看下这个方法的定义。

public static void loop() {
    final Looper me = myLooper();
    // .. 无关代码
    final MessageQueue queue = me.mQueue;
    // .. 无关代码
    for (;;) {
        Message msg = queue.next(); // 可能会 bolck
        if (msg == null) {
            return;
        }
        // ... 无关代码
        final long dispatchEnd;
        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        // ... 无关代码
        msg.recycleUnchecked();
    }
}

从上面我们可以看出,当该方法被调用的时候,它会先开启一个无限循环,并在该循环中使用 MessageQueue 的 next() 方法来取出下一个消息并进行分发。这里我们先不看 next() 方法的定义。我们先把这个方法中涉及的部分分析一下。

当获取到了下一个消息之后,会调用它的target 也就是发送该消息的 Handler 的 dispatchMessage() 方法来进行处理。该方法的定义如下:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

从上面可以看出,如果该消息是通过包装 Runnable 得到的话,会直接调用它的 handleCallback() 方法进行处理。在该方法内部会直接调用 Runnable 的 run() 方法。因为比较见到那,我们就补贴出代码了。

然后,会根据 mCallback 是否为空来决定是交给 mCallback 进行处理还是内部的 handleMessage() 方法。这里的 mCallback 是一个接口,可以在创建 Handler 的时候通过构造方法指定,也比较简单。而这里的 handleMessage() 方法,我们就再熟悉不过了,它就是我们创建 Handler 的时候重写的、用来处理消息的方法。这样,消息就被发送到了我们的 Handler 中进行处理了。

以上就是消息被处理的过程,代码的逻辑还是比较清晰的。下面我们就来看下 MessageQueue 是如何获取 “下一个” 消息的。

2.4.3 MessageQueue 的消息管理

上面我们已经分析完了 Handler 发送的消息执行的过程。这里我们在来分析一下其中的获取 “下一个” 消息的逻辑:

Message next() {
    // 如果消息循环已经停止就直接返回。如果应用尝试重启已经停止的Looper就会可能发生这种情况。
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 调用 native 的方法,可能会这个函数发生 block
        nativePollOnce(ptr, nextPollTimeoutMillis);
        // ... 无关代码
    }
}

从上面可以看出 Java 层的 MessageQueue 的 next() 方法是一个循环。除了获取消息队列之外,还要监听 Natvie 层 Looper 的事件触发。通过调用 native 层的 nativePollOnce() 方法来实现。该方法内部又会调用 NativeMessageQueue 的 pollOnce() 方法。而且注意下,在下面的方法中,nativeMessageQueue 是从 Java 层的 mPtr 中获取到的。所以我们说,在初始化 MessageQueue 的时候得到的 mPtr 起到了桥梁的作用:

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

在 NativeMessageQueue 的 pollOnce() 方法中会调用 native 层的 Looper 的 pollOnce(),并最终调用 native 层 Looper 的 pollInner() 方法:

int Looper::pollInner(int timeoutMillis) {
    // ... 根据下一个消息的事件调整超时时间
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;
    mPolling = true; // 将要空闲
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    // 待已注册之事件被触发或计时终了
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    mPolling = false; // 不再空闲
    mLock.lock(); // 请求锁
    if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;
        rebuildEpollLocked(); // 根据需要重建 epoll
        goto Done;
    }
    // 进行检查
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        result = POLL_ERROR; // 错误
        goto Done;
    }
    if (eventCount == 0) {
        result = POLL_TIMEOUT; // 超时
        goto Done;
    }
    // 处理所有消息
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd.get()) { // 唤醒 fd 有反应
            if (epollEvents & EPOLLIN) {
                awoken(); // 已经唤醒了,则读取并清空管道数据
            }
        } else {
            // 其他 input fd 处理,其实就是将活动 fd 放入到 responses 队列中,等待处理
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
                // 将消息放进 mResponses 中
                pushResponse(events, mRequests.valueAt(requestIndex));
            }
        }
    }
Done: ;
    // 触发所有的消息回调,处理 Native 层的Message
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            { // 获取 handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();
                handler->handleMessage(message);
            } // 释放 handler
            mLock.lock();
            mSendingMessage = false;
            result = POLL_CALLBACK;
        } else {
            // 队列头部的消息决定了下个唤醒的时间
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }
    mLock.unlock(); // 释放锁
    // 触发所有的响应回调
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq); // 移除文件描述符
            }
            response.request.callback.clear();
            result = POLL_CALLBACK;
        }
    }
    return result;
}

从上面我们可以看出 Native 层的 pollInner() 方法首先会根据 Java 层传入的 timeoutMillis 调用 epoll_wait 方法来让线程进入等待状态。如果 timeoutMillis 不为 0,那么线程将进入等待状态。如果有事件触发发生,wake 或者其他复用 Looper 的 event,处理event,这样整个 Native 层的 Looper 将从 block 状态中解脱出来了。这样回到 Java 层就将继续执行 MessageQueue 中下一条语句。至于 Native 层的 Looper 何时从 block 状态中醒过来,就需要根据我们入队的消息来定。也就用到了 MessageQueue 的 enqueueMessage() 方法的最后几行代码:

if (needWake) {
    nativeWake(mPtr);
}

即:只有当当前的头结点消息之前存在栅栏 (barrier) 并且新插入的消息是最先要被触发的异步消息就进行唤醒。

上面主要是 Native 层的 Looper 线程 block 的相关的逻辑。即当我们获取消息队列的下一条消息的时候会根据下一个消息的时间来决定线程 block 的时长。当我们将一个消息加入到队列的时候会根据新的消息的时间重新调整线程 block 的时长,如果需要的话还需要唤起 block 的线程。当线程从 block 状态恢复出来的时候,Java 层的 Looper 就拿到了一个消息,对该消息进行处理即可。

3、总结

在上文中,我们从 Java 层到 Native 层分析了 Handler 的作用的原理。这里我们对这部分内容做一个总结。

3.1 Handler、MessageQueue 和 Looper 之间的关系

首先是 Handler、MessageQueue 和 Looper 之间的关系。我们用下面的这个图来表示:

MessageQueue Handler Looper

也就是说,一个线程中可以定义多个 Handler 实例,但是每个 Handler 实际上引用的是同一个 Looper。当然,我们要在创建 Handler 之前先创建 Looper。而每个 Looper 又只对应一个 MessageQueue。该 MessageQueue 会在创建 Looper 的时候被创建。在 MessageQueue 中使用 Message 对象来拼接一个单向的链表结构,依次来构成一个消息队列。每个 Message 是链表的一个结点,封装了我们发送的信息。

3.2 Handler 的消息发送过程

然后,我们再来分析下 Handler 中的消息是如何被发送的。同样,我们使用一个图来进行分析:

Handler 的消息发送过程

根据上文的内容我们将 Handler 发送消息的方法分成 post 和 send 两种类型。post 的用来发送 Runnable 类型的数据,send 类型的用来发送 Message 类型的数据。但不论哪种类型最终都会调用 Handler 的 sendMessageAtTime() 方法来加入到 MessageQueue 的队列中。区别在于,post 类型的方法需要经过 Handler 的 getPostMessage() 包装成 Message 之后再发送。

3.3 Looper 的执行过程

当消息被添加到队列之后需要执行消息,这部分内容在 Looper 的 loop() 方法中。但是这部分内容稍显复杂,因为涉及 Native 层的一些东西。我们这里仍然使用图来进行描述:

Looper 的执行过程

当我们调用 Looper 的 loop() 方法之后整个 Looper 循环就开始不断地处理消息了。在上图中就是我们用绿色标记的一个循环。当我们在循环中调用 MessageQueue 的 next() 方法来获取下一个消息的时候,会调用 nativePollOnce() 方法,该方法可能会造成线程阻塞和非阻塞,当线程为非阻塞的时候就会从 Native 层回到 Java 层,从 MessageQueuue 中取得一个消息之后给 Looper 进行处理。如果获取的时候造成线程阻塞,那么有两种情况会唤醒阻塞的线程,一个是当一个新的消息被加入到队列中,并且将会早于之前队列的所有消息被触发,那么此时将会重新设置超时时间。如果达到了超时时间同样可以从睡眠状态中返回,也就回到了 Java 层继续处理。所以,Native 层的 Looper 的作用就是通过阻塞消息队列获取消息的过程阻塞 Looper。

3.4 最后

因为本文中不仅分析了 Java 层的代码,同时分析了 framework 层的代码,所以最好能够结合两边的源码一起看,这样更有助于自己的理解。在上面的文章中,我们给出了一些类的在线的代码链接,在 Google Source 上面,需要 VPN 才能浏览。另外,因为笔者水平有限,难免存在有误和不足的地方,欢迎批评指正。


我是 WngShhng. 如果您喜欢我的文章,可以在以下平台关注我:

Android 总结:Message,MessageQueue,Looper,Handler 消息机制 小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!资料⬅专栏获取。 阅读详情

相关推荐

Android的消息机制学习(一)Looper,Handler,MessageQueue

Message,Handler是在Android中最常用的,更新UI点手段。与其他图形界面的原理类似,Android系统中UI也是靠消息驱动来工作的,具体有以下一些概念。 消息发送者:发生消息到队列 消息队列:存储消息的队列 消息循环:不断的循环取出消息,发给处理者 消息处理者:处理消息

云涛的博客 1191

Handler、MessageQueue、Looper底层原理梳理(详解)

本文深入分析了Android消息处理机制的核心组件:Handler、Looper、Message和MessageQueue的工作原理及相互关系。详细列举了安卓源码,对理解安卓的通信机制有重要意义。解释的非常详细,非常适合刚入门的小伙伴。

appron的博客 1343

Android之Handler、Message、MessageQueue、Looper详解

Android之Handler、Message、MessageQueue、Looper详解

是阿超 3526

从源分析Handler、MessageQueue、Looper

很长的一段时间我一直在使用Handler,主要是在处理异步任务的时候来实现线程切换,不过对其原理和工作流程并没有详细的做过了解,以下我把从分析源码得到一些内容做出了一些总结。

MasterSkills的博客 176

程序化广告行业(23/89):深度剖析广告效果评估指标

大家好,我一直希望能和大家一起在技术领域不断探索、共同进步。在之前的文章中,我们了解了程序化广告中腾讯的业务布局和广告效果评估的基础概念,今天让我们进一步深入剖析广告效果评估的各项指标,这对理解程序化广告的运作机制至关重要

yyy173611的博客 4862

人工智能学习之机器人路径规划优化

A* AStar 人工智能 机器人路径规划优化

RongLin02的博客 7073

android Handler Looper,MessageQueue消息机制原理

安卓消息处理类: Looper、Handler、MessageQueue、Message、ThreadLocal、ThreadLocal.Values、HandlerThread。 Looper: 线程默认是没有消息循环的,要为一个线程创建一个消息循环通过调用prepare(),然后在调用loop()方法进入消息循环(这意味着线程将一直在此方法循环)。例如: class LooperThr

ZhouLi_优快云的专栏 9992

EEGLAB安装、EDF数据导入、EEG预处理(Matlab)

目录下载和安装下载安装遇到问题及解决方案导入EDF数据EEG预处理 下载和安装 下载 链接:https://pan.baidu.com/s/17eMT6B7LgZ4PyqWvD5ixmg 提取码:12gz 安装 **第一步:**将下载下来的文件解压,修改名称为eeglab,将此文件夹存放在matlab的安装目录里的toolbox文件夹下,下图是我的路径,需要拷贝到自己相对应的路径中 第二步: 打开Matlab软件,点击主页中的设置路径 设置路径 点击添加并包含子文件夹,将eeglab文件夹添加进来,保

y18771025420的博客 1万+

Android消息机制(Handler、Looper、MessageQueue)

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。

walk的博客 1075

XTDrone——ALOAM与3D激光雷达三维SLAM(处理没有点云和坐标系报错的问题)

本文介绍了XTDrone中配置如何在三维环境中使用A-LOAM算法配合velodyneHDL32激光雷达包括依赖包安装、代码修改和PX4系统配置,以及相关报错细节的注意事项。

weixin_45390670的博客 1325

图像传感器与信号处理——详解CCD与CMOS图像传感器

一文详解CCD与CMOS图像传感器一文详解CCD与CMOS图像传感器1. 图像传感器性能关键词1.1 噪声1.2 暗电流 一文详解CCD与CMOS图像传感器 本文主要总结了CCD与CMOS传感器的原理知识,参考书籍为《数码相机中图像传感器和信号处理》,英文版为《Image Sensors And Signal Processing for Digital Still Cameras》 1. 图像传...

weixin_44580210的博客 2万+

Android消息机制原理详解(Looper、Handler、MessageQueue)

Android的消息机制主要是指Handler的运行机制,Handler的运行机制需要底层MessageQueue和Looper的支撑。 Handler:消息的执行者,也可以称之为异步任务的执行者Message:消息的封装者,把异步任务,消息码Handler对象等封装成Message对象MessageQueue:消息队列,用于保存当前线程的所有消息Message对象的一个列表,内部储

小码农大梦想 797

DICOM标准:DICOM医学影像中的覆盖层(Overlay)概念详解

DICOM标准:深入详解DICOM医学影像中的覆盖层(Overlay)

编程技术探索者,分享C/C++、C#、Java、数据库等开发经验,聚焦实战技巧与AI兴趣,助力编程爱好者成长。 1561

Android消息机制回顾(Handler、Looper、MessageQueue源码解析)

结合Handler、Looper、MessageQueue源码介绍Android消息机制。通过Handler 消息机制来解决线程之间通信问题,或者用来切换线程。

一个XR(AR|VR|MR)程序猿的博客(EQ-雪梨蛋花汤) 1276

大数据在电力行业的应用案例解析-【电力技术】(九)大数据在电力储能运维中的深度应用(含故障预测代码)

大数据技术为电力储能运维提供了“精准化、智能化、高效化”的解决方案,破解了传统运维的核心痛点,实现了储能系统从“被动抢修”到“主动运维”的转型,为新型电力系统建设提供了重要支撑。当前,随着储能产业的快速发展,大数据与储能运维的融合将更加深入,未来将结合人工智能、物联网、区块链等技术,实现储能运维的全流程智能化——从设备故障的提前预警,到寿命的精准预测,再到运维策略的自动优化、调度协同的实时响应,进一步提升储能系统的安全性、经济性与可靠性。

getusushu的博客 86

【CT/电镜 重建教程 1.1】层析成像的精确实空间迭代重建算法(RESIRE)

在此,我们报告了RESIRE算法的发展用于准确的断层重建。与实空间迭代算法相比,RESIRE使用傅里叶切片定理或Radon变换作为正向投影,使用线性变换作为反向投影,两者都具有高精度。与EST和GENFIRE相比,RESIRE是一种实空间迭代算法,可以在并行计算中实现快速运行时间。

2401_87064292的博客 1060

Android 消息机制 - Handler, Looper, Message, MessageQueue 的源码分析

转载请注明本文出自 clevergump 的博客:http://blog.youkuaiyun.com/clevergump/article/details/50776596, 谢谢! 一. 前言 Android 的消息系统, 主要涉及到四个类: Handler, Message, MessageQueue 和 Looper. 而我们在日常开发中, 通常是将 Handler 对象定义在主线程中用于更新UI, 这样..

clevergump的专栏 2139

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Android消息处理机制——Looper,Handler,Message,MessageQueue

题外话: 过去半年都没有

Abel Joo 尘归尘,土归土。 1696

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

一张图秒懂Android消息机制(图+源码):Handler、Looper、MessageQueue

一张简单的图解释消息机制原理,秒懂!结合源码分析,逐个介绍消息机制的组成成员Handler、Looper、ThreadLocal、MessageQueue、Message。

cheneasternsun的博客 717

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

通过源码简要分析之Android消息机制Handler、Looper、MessageQueue运行机制

           用了许久的Handler,对于Handler的使用确实是比较熟悉,但是具体内部是如何运作的,却只是模糊的。Handler发出的消息怎么到达MessageQueue?MessageQueue的数据怎么被轮循处理?Looper是怎么循环取MessageQueue的消息的。今天,通过在子线程中创建Looper的实例,以追溯源码的方式来分析这个流程,通过这种方式来理解Android...

慢行的狮子 758
上一篇: Java 注解及其在 Android 中的应用
下一篇: RxJava2 系列-1:一篇的比较全面的 RxJava2 方法总结
开发者如是说
博客等级 码龄10年 67粉丝 163原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值