从 C 链表到 Android Looper:MessageQueue 的底层原理一条线讲透

前言:为什么要从 C 链表讲起?

很多 Android 开发者都用过 Handler / Looper / MessageQueue

但一深入源码就会觉得"抽象、复杂、难以下手"。

其实问题不在 Android,而在于我们没把底层模型串起来

如果你愿意退回一步,用 C 语言最基础的数据结构视角 去看,就会发现:

Android 的消息机制,本质就是:
链表 + 队列 + 排序 + 阻塞循环。

本文尝试做一件事:

👉 从 C 语言的链表出发,一步一步推导到 Android 的 MessageQueue / Looper

把这条逻辑链完整走一遍。

一、指针的本质:一切从"地址"开始

在 C 语言中:

复制代码
int x = 10;
int *p = &x;
  • x 是一个值
  • &x 是 x 的内存地址
  • p 是一个存地址的变量
  • *p 表示"通过地址访问那块内存里的值"

指针 = 存地址的变量

这是后面所有数据结构的根基。

这一点非常重要,因为:

  • 链表
  • 队列
  • MessageQueue
  • Looper

全部建立在"地址关系"之上。

二、为什么需要 struct?

单个变量无法表达复杂对象,我们需要把"相关数据"组织在一起:

复制代码
struct Person {
    int age;
    int height;
};

struct 的本质只是:

👉 一块内存的布局说明书

它本身并不负责逻辑。

三、Node:链表的最小原子结构

链表的核心是 Node

复制代码
typedef struct Node {
    void *data;        // 指向真实数据
    struct Node *next; // 指向下一个节点
} Node;

这里有两个完全不同层次的指针:

  • data业务数据指针

  • next结构关系指针

一句话总结:

Node = 数据 + 指向下一个 Node 的关系

多个 Node 通过 next 串起来,就形成了链表:

复制代码
Node1 -> Node2 -> Node3 -> NULL

四、为什么"只有 Node"是没用的?

此时会遇到一个致命问题:

👉 从哪里开始遍历?

链表必须有一个"入口",也必须有人维护整体状态。

五、Queue:Node 的管理者(系统思维的起点)

于是我们引入"管理结构":

复制代码
typedef struct {
    Node *head;
    Node *tail;
    int   size;
} Queue;

现在结构关系变成:

复制代码
Queue
 ├── head ──> Node ──> Node ──> Node ──> NULL
 ├── tail ────────────────────────────┘
 └── size

注意一个非常重要的事实:

Queue 自己不存数据,它只负责"管理 Node 的关系"。

这是从"数据结构"走向"系统设计"的第一步。

六、为什么 Queue* 不需要二级指针?

初始化 Queue 通常这样写:

复制代码
Queue q;
queue_init(&q);

void queue_init(Queue *q) {
    q->head = NULL;
    q->tail = NULL;
    q->size = 0;
}

这里:

  • q 本体已经存在(在栈上)

  • 函数只是修改 Queue 内部字段

  • 并没有修改指针变量本身

所以:

👉 一级指针足够

七、什么时候才需要二级指针?

只有一种情况:

当函数需要"创建 / 替换一个指针变量本身"

复制代码
void create_queue(Queue **pq) {
    *pq = malloc(sizeof(Queue));
}

调用方式:

复制代码
Queue *q = NULL;
create_queue(&q);

这里的本质是:

  • q 是一个 Queue* 指针变量

  • &q 的类型是 Queue**

  • Queue** 正好能"接住" &q

  • *pq = malloc(...) 本质是给 q 重新赋值

👉 二级指针的本质是"类型匹配 + 写回指针变量"

八、从 Queue 到 MessageQueue:关键差异只有一个

普通 Queue 是 FIFO

Android 的 MessageQueue 不一样,它是:

按执行时间排序的消息队列

因此 Node 演化为 Message:

复制代码
typedef struct Message {
    long when;              // 什么时候执行
    void (*callback)(void); // 要执行的任务
    struct Message *next;
} Message;

你会发现:

  • 结构没变

  • 指针没变

  • 只是数据字段更"业务化"

九、MessageQueue 的核心职责

MessageQueue 主要做三件事:

  1. when 有序插入 Message

  2. 维护单向链表

  3. 提供 next() 获取"当前可执行的消息"

它不是简单的队列,而是"时间有序链表"。

十、Looper:系统的"心跳循环"

Looper 的逻辑可以简化成一句话:

复制代码
for (;;) {
    Message *msg = queue.next();
    dispatch(msg);
}

也就是说:

Looper = 无限循环 + 从 MessageQueue 取消息并执行

这就是 Android UI 线程的"发动机"。

十一、为什么 Looper 不会空转卡死?

关键在 MessageQueue.next()

  • 如果队列为空

  • 或最近一条消息还没到执行时间

👉 线程进入阻塞状态

当:

  • 新消息入队

  • 或时间到达

👉 线程被唤醒

因此:

next() ≠ pop()
next() = "能执行才返回,否则阻塞等待"

这是系统层设计的精髓。

十二、完整映射关系一览

C 世界 Android 世界
Node Message
next 指针 Message.next
Queue MessageQueue
for(;;) Looper.loop()
阻塞等待 native poll / wake
相关推荐
方白羽2 小时前
Android 中Flags从源码到实践
android·app·客户端
深蓝电商API2 小时前
从数据采集到商业变现:网络爬虫技术的实战与边界
android·爬虫
夏乌_Wx2 小时前
练题100天——DAY28:找消失的数字+分发饼干
数据结构·算法
lzh200409192 小时前
二叉搜索树与双向链表
数据结构·链表
WolfGang0073212 小时前
代码随想录算法训练营Day48 | 108.冗余连接、109.冗余连接II
数据结构·c++·算法
45288655上山打老虎3 小时前
List容器
数据结构·windows·list
LYFlied3 小时前
【每日算法】LeetCode 234. 回文链表详解
算法·leetcode·链表
NeDon4 小时前
[OJ]数据结构:移除链表元素
c语言·数据结构·算法·链表
承渊政道4 小时前
一文彻底搞清楚链表算法实战大揭秘和双向链表实现
c语言·数据结构·算法·leetcode·链表·visual studio