从 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
相关推荐
阿巴斯甜17 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker18 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952719 小时前
Andorid Google 登录接入文档
android
黄林晴20 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android