深入理解安卓消息队列机制(1)Native层 Looper

0 背景

在操作系统中,消息队列处理框架是操作系统为应用开发者提供的最基本的功能。

在 Android 中,消息队列通过 Looper / Message / MessageHandler 等native 对象提供,并通过 JNI 封装了 Java API 供上层应用使用。

本文重点分析 Looper.cpp native 相关代码,从底层分析 Android 的消息队列处理框架。

bash 复制代码
/system/core/libutils/Looper.cpp
/system/core/libutils/Looper.h

1 数据模型

消息队列需要包含如下模型:消息,循环队列,消息处理回调: Native Looper 中的各个模块关系如下:

1.1 Message 消息:

仅包含一个int 字段,what,表示:这是一个什么消息

scss 复制代码
/**
 * A message that can be posted to a Looper.
 */
struct Message {
    Message() : what(0) { }
    Message(int w) : what(w) { }
    /* The message type. (interpretation is left up to the handler) */
    int what;
};

1.2 MessageHandler 消息处理器:

收到消息如何处理,所以仅仅定义了一个 virtual 方法:handleMessage

arduino 复制代码
/**
 * Interface for a Looper message handler.
 *
 * The Looper holds a strong reference to the message handler whenever it has
 * a message to deliver to it.  Make sure to call Looper::removeMessages
 * to remove any pending messages destined for the handler so that the handler
 * can be destroyed.
 */
class MessageHandler : public virtual RefBase {
protected:
    virtual ~MessageHandler();
public:
    /**
     * Handles a message.
     */
    virtual void handleMessage(const Message& message) = 0;
};

1.3 Looper::MessageEnvelope:

定义在 Looper 类内部的一个私有类,把 Message 和 MessageHandler 关联在了一起,并记录上消息的处理时间

scss 复制代码
    struct MessageEnvelope {
        MessageEnvelope() : uptime(0) { }
        MessageEnvelope(nsecs_t u, sp<MessageHandler> h, const Message& m)
            : uptime(u), handler(std::move(h)), message(m) {}
        nsecs_t uptime;
        sp<MessageHandler> handler;
        Message message;
    };

1.4 Looper::mMessageEnvelopes:

真正的 "消息队列",用 Vector 保存 Looper 收到的所有信息,在 Looper 被唤醒以后,依次处理

kotlin 复制代码
class Looper {
private:
    Vector<MessageEnvelope> mMessageEnvelopes
}

1.5 Looper

封装了所有"消息队列" 相关的API 和数据结构,重要 API 如下:

arduino 复制代码
class Looper {
public:
    // Looper 的构造函数,线程唯一
    static sp<Looper> prepare(int opts);// 
    static void setForThread(const sp<Looper>& looper);
    static sp<Looper> getForThread();
    // 等待消息的到来,处理消息
    int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
    // 唤醒 Looper 线程,pollOnce 的 epoll_wait 收到信号,开始执行处理流程
    void wake();
    // 发送消息的 API, 支持定时发送和延迟发送
    void sendMessage(const sp<MessageHandler>& handler, const Message& message);
    void sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,
            const Message& message);
    void sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
            const Message& message);
    // 取消发送的消息
    void removeMessages(const sp<MessageHandler>& handler);
    void removeMessages(const sp<MessageHandler>& handler, int what);
}

从上面的代码片段可知,Looper 的 API 包含4大类:

1 Looper::prepare 为当前线程创建 Looper 对象

2 pollOnce 开始通过 epoll_wait 持续等待消息, 如果等到消息后,调用对应 Message 的 MessageHandler.handleMessage 进行处理

3/4 sendMessage removeMessage 是 Looper 消息队列的发送和取消接口

2 Looper::prepare 流程

Looper::prepare 为当前的线程建立 Looper 对象,简化后的 prepare 如下:

step 1: Looper::getForThread() 查看当前线程是否已经有 Looper,

step 2: 如果没有,new 一个 Looper 并保存到当前线程

ini 复制代码
sp<Looper> Looper::prepare(int opts) {
    // 第一次调用,生成一个 TLS key,并根据 key 查询是否有对应线程的 Looper
    sp<Looper> looper = Looper::getForThread();
    // 如果没有,说明是第一次调用,需要初始化 Looper
    if (looper == nullptr) {
        looper = new Looper();
        // 保存 Looper 对象到 TLS 中
        Looper::setForThread(looper);
    }
    return looper;
}

2.1 Looper 的线程唯一特性

TLS 可以理解为以线程为 key 的 HashMap,每个线程保存一个唯一的 Looper 对象。具体实现,依赖的是 pthread 提供的 thread local 特性:

pthread_setspecificpthread_getspecific: 这两个函数用于在特定线程中设置和获取线程局部存储(TLS)键的值。这样,每个线程都可以有一个独立的Looper对象。

scss 复制代码
pthread_setspecific(gTLSKey, looper.get());
pthread_getspecific(gTLSKey);

其中,使用 pthread_key_create 生成一个唯一的 key

复制代码
pthread_key_create

pthread_once 保证 initTLSKey 仅生成一次

scss 复制代码
pthread_once(& gTLSOnce, initTLSKey);
scss 复制代码
void Looper::initTLSKey() {
    int error = pthread_key_create(&gTLSKey, threadDestructor);
}
void Looper::threadDestructor(void *st) {
    Looper* const self = static_cast<Looper*>(st);
    if (self != nullptr) {
        self->decStrong((void*)threadDestructor);
    }
}
void Looper::setForThread(const sp<Looper>& looper) {
    sp<Looper> old = getForThread(); // also has side-effect of initializing TLS
    if (looper != nullptr) {
        looper->incStrong((void*)threadDestructor);
    }
    pthread_setspecific(gTLSKey, looper.get());
    if (old != nullptr) {
        old->decStrong((void*)threadDestructor);
    }
}
sp<Looper> Looper::getForThread() {
    int result = pthread_once(& gTLSOnce, initTLSKey);
    return (Looper*)pthread_getspecific(gTLSKey);
}

2.2 Looper 建立 epoll 相关的事件监听

Looper 对象创建时,初始化了 epoll 相关功能:

scss 复制代码
Looper::Looper() {
    // mWakeEventFd 用于唤醒 Looper 线程中的 epoll_wait
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
    // 建立 epoll_wait
    rebuildEpollLocked();
}

其中:rebuildEpollLocked 建立 epoll 事件监听:

第一次 Looper::prepare 时,新建 epoll 对象,仅仅监听一个 fd -- mWakeEventFd

ini 复制代码
void Looper::rebuildEpollLocked() {
    // Allocate the new epoll instance and register the wake pipe.
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd.get();
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
}

后续可以通过 addFd 添加更多 fd 的监听,Android 的 Message / Handler / Looper 机制,仅使用了 mWakeEveentFd 这一个 fd

3 Looper 发送消息流程

3.1 Looper::sendMessage

向消息队列发送一个消息,使用 sendMessage 系列 API,有三个参数:

a. nsecs_t uptime: 消息的分发时间,Looper 中的消息队列,按照时间顺序排列

b. const sp& handler, 消息处理函数,Looper 调用对应消息的 handleMessage 方法处理对性消息

c. const Message& message, 消息本身,只有一个 what 参数

发送具体过程可以分为三步:详见下面注释:

arduino 复制代码
void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
        const Message& message) {
    // Step 1: 按照发送时间,找到消息需要插入的位置    
    size_t i = 0;
    size_t messageCount = mMessageEnvelopes.size();
    while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {
        i += 1;
    }
    // Step 2: 消息封装为 MessageEnvelope,插入到 vector mMessageEnvelopes
    MessageEnvelope messageEnvelope(uptime, handler, message);
    mMessageEnvelopes.insertAt(messageEnvelope, i, 1);
    // Step 3: 如果插入到了第一个位置,唤醒 Looper 线程进行处理
    // Wake the poll loop only when we enqueue a new message at the head.
    if (i == 0) {
        wake();
    }
}

3.1 Looper::wake

其中, wake() 方法使用了构造函数中新建的 fd: mWakeEventFd

c 复制代码
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 (returned %zd): %s",
                             mWakeEventFd.get(), nWrite, strerror(errno));
        }
    }
}

也就是通过 write() 系统调用,给 mWakeEventFd 写入了一个 1

由于在 Looper 创建时, epoll 对象在监听 mWakeEventFd 的变化。因此,当写入 1 时,epoll_wait 函数就会收到对应的事件,被唤醒。

4 Looper 处理消息流程

4.1 Looper::pollOnce

在 Looper::prepare 创建 Looper 对象后,调用方就会执行 Looper::pollOnce 启动消息循环。

arduino 复制代码
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        ...... // 处理 Response 相关事件的逻辑暂不分析
        result = pollInner(timeoutMillis);
    }
}

从上面代码中,可以看到这是一个死循环。从这里也诞生了一个著名的面试问题:

复制代码
Looper 一直在主线程中死循环,为什么不会造成 ANR

4.2 Looper::pollInner

我们进入 pollInner 中分析一下消息处理逻辑:

第一部分逻辑是如下: epoll_wait 部分,在 Looper::prepare 中,我们建立了对 mWakeEventFd 的监听。

一旦客户端有调用 sendMessage 相关逻辑,epoll_wait 就会返回,否则,线程卡死在 epoll_wait 过程。

ini 复制代码
int Looper::pollInner(int timeoutMillis) {
    // We are about to idle.
    mPolling = true;
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    // No longer idling.
    mPolling = false;
    ......
}

例如:在ANR 的 trace 日志中,我们经常可以看到,有大量线程处于 nativePollOnce 状态,也就是等待事件到达。

less 复制代码
"main" prio=5 tid=1 Native
  | group="main" sCount=1 ucsCount=0 flags=1 obj=0x73145898 self=0xb400006f3b6e2be0
  | sysTid=1866 nice=0 cgrp=foreground sched=0/0 handle=0x70902df4f8
  | state=S schedstat=( 918826499730 201286858402 3920982 ) utm=56932 stm=34949 core=3 HZ=100
  | stack=0x7fc6fc2000-0x7fc6fc4000 stackSize=8188KB
  | held mutexes=
  native: #00 pc 00000000000ad7a8  /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8) (BuildId: 45cceace7cf5c8c8a5a9c2064fa4532f)
  native: #01 pc 0000000000017ebc  /system/lib64/libutils.so (android::Looper::pollInner(int)+188) (BuildId: 10aac5d4a671e4110bc00c9b69d83d8a)
  native: #02 pc 0000000000017da0  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+112) (BuildId: 10aac5d4a671e4110bc00c9b69d83d8a)
  native: #03 pc 000000000016058c  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44) (BuildId: 92a456bea4f19ed89af37ce405ef942c)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:335)
  at android.os.Looper.loopOnce(Looper.java:167)
  at android.os.Looper.loop(Looper.java:283)
  at android.app.ActivityThread.main(ActivityThread.java:8186)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:573)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1015)

第二部分逻辑是,epoll_wait 收到事件后,如果是mWakeEventFd 的事件,直接调用 awoken()清理状态,表示事件已经收到。

ini 复制代码
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd.get()) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } 
    }

这里可以看到,Looper 可以监控多个 fd 的事件,最多可以监控 16个 (EPOLL_MAX_EVENTS = 16)。

第三部分逻辑:

从 mMessageEnvelopes 消息队列中,依次取出消息,如果消息事件已经超时,调用 MessageHandler 对应的 handleMessage 方法,处理消息。

ini 复制代码
    // Invoke pending message callbacks.
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes.  Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();
                handler->handleMessage(message);
            } // release handler
            mLock.lock();
            mSendingMessage = false;
            result = POLL_CALLBACK;
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

4.3 Q:为什么 Looper 一直在主线程中死循环确不会ANR

为什么 Looper 一直在主线程中死循环,为什么不会造成 ANR?

1 从上面的分析中可以看到,主线程一直处于 epoll_wait 等待消息的状态,并不会消耗 CPU 资源。

2 ANR 可以认为是 system_server 监控客户端进程是否响应的看门狗机制,nativePollOnce 不会触发任何一种类型的 ANR Timer 超时。

为什么我们在 onCreate 或者 onResume 中死循环,会造成 ANR:

因为, AMS 在发送 scheduleCreate 或者 scheduleResume 事件时,启动了 ANR 定时器,客户端主线程卡住会造成定时器超时。

5 总结

本文分析了Android 最基础的 Looper Message MessageHandler 机制,这是 Android 应用框架和操作系统的基础。

通过对 C++ 代码的分析,我们可以对上层 Java 代码相关API的功能理解的更为深刻。

Looper 中的 epoll 除了可以监控默认的 mWakeEventFd 还可以最多监控 16 个其他的 fd。限于篇幅,本文就不再继续分析。

相关推荐
ALex_zry42 分钟前
程序运行报错分析文档
android·c++
投笔丶从戎2 小时前
Kotlin Multiplatform--04:经验总结(持续更新)
android·开发语言·kotlin
stevenzqzq2 小时前
kotlin flow的写法
android·kotlin·flow
悠哉清闲2 小时前
Kotlin 协程 (三)
android·开发语言·kotlin
androidwork2 小时前
在 Android 中实现支持多手势交互的自定义 View(Kotlin 完整指南)
android·kotlin·交互
悠哉清闲2 小时前
Kotlin 协程 (二)
android·开发语言·kotlin
androidwork2 小时前
使用 ARCore 和 Kotlin 开发 Android 增强现实应用入门指南
android·kotlin
群联云防护小杜3 小时前
企业级物理服务器选型指南 - 网络架构优化篇
服务器·网络·架构
乌旭3 小时前
去中心化算力池:基于IPFS+智能合约的跨校GPU资源共享平台设计
人工智能·深度学习·架构·去中心化·区块链·智能合约·risc-v
Ryannn_NN4 小时前
avalonia android连接模拟器时报错adb cannot run as root in production builds,没有权限
android·adb·wpf·xamarin