# 题记

本文又几年前给同事们培训准备的资料整理而来,只是凭借印象,粗略记录框架原理和要点,需要新人花不少功夫自学探索。待闲暇时,待我重新阅读源码,再详细展开。读懂消息循环,需要具备英语阅读能力,如果尚缺,可以阅读英语自学指南

# 用法

线程是不具备消息循环的,需要调用 Looper.prepare() & Looper.loop() 才开启消息循环。系统已经在 ActivityThread 帮忙主线程开启了消息循环,代码如下。

public static void main(String[] args) {
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
          if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

所以,主线程是不可以调用 looper.quit() ,不然抛出 new RuntimeException("Main thread loop unexpectedly exited") ,而 WorkerThread 如果开启消息循环,不再需要时,得调用 looper.quit() ,否则内存泄漏。

# 类图


每个线程都有自己的消息循环机制和队列,实现的关键是将 Looper 对象保存在当前 ThreadThreadLocalMap ,<key, value> = <Looper.sThreadLocal, Looper>.

android.os.Looper#prepare(boolean)

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

java.lang.ThreadLocal#set

/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

# Overview

消息队列的消息有指定执行时间,消息循环机制是如何做到准时处理消息的?

  1. 消息循环机制初始化时,在 native 层,监听 `mWakeEventFd。
  2. 消息循环开始时,从消息队列里取消息,自然取不到,就在 native 等待 mWakeEventFd 事件
  3. handler 发送消息,并进入消息队列,native 层 在 mWakeEventFd 写入数据,唤醒第二步的等待事件。
  4. 第二步从等待中唤醒,读取消息队列的消息,如果消息执行时间是现在,则处理消息。否则计算执行时间和当前的时间差 nextPollTimeoutMillis ,重新插入消息队列,并在 native 层等待,等待超时参数是 nextPollTimeoutMillis

# nextPollTimeoutMillis

nextPollTimeoutMillis 看似不起眼,其实是很重要的变量,它有三种类型的值,分别代表不同的含义。

  1. -1:消息队列没消息,native 使用 wait()
  2. 0:(TODO: )
  3. 自然数:消息队列最早执行的消息(队列头部的消息)在未来,当前没有消息处理,可以歇息。native 使用 wait(int timeout) ,超时后自动从等待中返回,无需 write 事件唤醒。

android.os.MessageQueue#next

int nextPollTimeoutMillis = 0;
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);
            // Try to retrieve the next message.  Return if found.
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    return msg;// Got a message.
                }
             } else {
                    nextPollTimeoutMillis = -1;// No more messages.
              }
            // A new message could have been delivered, so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }

# Prepare


使用 Epoll API,在 mWakeEventFd 注册监听

# Loop And Handle Message


此时消息队列还没有消息,在 epoll_wait 等待

# Insert Message


往消息队列插入消息后,往 mWakeEventFd 写入一个整数,唤醒 poll_wait ,从而 loop 循环中取出消息,接着处理 Message。

# 线程间通信


通信线程双方,持有对方的 handler 实例的引用,通过 handler.sendMessage 往对方线程的消息队列发送消息,如此两个线程就可以彼此发送消息,实现通信。

# 消息作用域


哪个 Handler 发送的消息,最后出消息队列,仍由该 Handler 处理,所以不同的 Handler 实例,可以有相同的 What,而不会相互干扰。具体实现是 handler.sendMessage(message) 时, handler 对象保存在 message.target 字段。

# ThreadLocal

# 雷区

# Reference

[1] https://medium.com/@copyconstruct/the-method-to-epolls-madness-d9d2d6378642
[2 ] https://man7.org/linux/man-pages/man7/epoll.7.html
[3] The Design of the UNIX Operating System
[4] /base/core/java/android/os/MessageQueue.java
[5] /frameworks/base/core/jni/android_os_MessageQueue.cpp
[6] /system/core/libutils/Looper.cpp

Edited on Views times

Give me a cup of [coffee]~( ̄▽ ̄)~*

文理兼修电脑首席 WeChat Pay

WeChat Pay

文理兼修电脑首席 Alipay

Alipay