news 2026/2/14 15:24:33

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从 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 世界
NodeMessage
next 指针Message.next
QueueMessageQueue
for(;;)Looper.loop()
阻塞等待native poll / wake
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/10 11:05:35

3个颠覆性突破让开源CMS成为中小企业数字化转型的秘密武器

在数字化转型浪潮中,中小企业的IT预算往往捉襟见肘,而Directus作为一款完全开源的内容管理平台,正以零许可成本和高度灵活的技术架构,为预算有限的企业提供了一条全新的数字化路径。这款基于Node.js构建的现代化CMS,不…

作者头像 李华
网站建设 2026/2/3 9:18:22

PapersGPT for Zotero 终极安装指南:5步快速配置AI文献助手

PapersGPT for Zotero 终极安装指南:5步快速配置AI文献助手 【免费下载链接】papersgpt-for-zotero Zotero chat PDF with DeepSeek, GPT, ChatGPT, Claude, Gemini 项目地址: https://gitcode.com/gh_mirrors/pa/papersgpt-for-zotero PapersGPT for Zotero…

作者头像 李华
网站建设 2026/2/7 16:03:36

15-2.【Linux系统编程】进程信号 - 信号保存(信号处理流程的三种状态:未决、阻塞、递达,信号保存由未决表完成、sigset_t信号集类型及相关函数)

目录3. 保存信号-内核通过 “未决信号集” 为每个进程存储已产生但未处理的信号3.1 信号处理流程中的不同状态3.2 信号在内核中的表示3.3 sigset_t信号集类型3.4 信号集操作函数3.4.1 sigprocmask读取或更改进程的信号屏蔽字3.4.2 sigpending读取当前进程的未决信号集3.4.3 综合…

作者头像 李华
网站建设 2026/2/3 6:51:48

31、国际化文本输入方法详解

国际化文本输入方法详解 1. 字体集与字符显示 当 XFontSet 缺少字符集时,每个不可用的字符会使用 XCreateFontSet 返回的默认字符串来绘制。对于无效码点的行为则未作定义。 2. 输入方法概述 输入方法涵盖多个方面,包括输入方法概述、管理、功能、值、输入上下文功能与值…

作者头像 李华
网站建设 2026/2/6 13:12:59

33、本地化与国际化文本函数详解

本地化与国际化文本函数详解 在处理国际化文本输入时,有许多关键的概念和函数需要我们去理解和掌握。下面将详细介绍这些内容,包括输入方法值、输入上下文函数以及输入上下文值等方面。 输入方法相关函数与值 XUnregisterIMInstantiateCallback 函数:该函数用于移除之前…

作者头像 李华
网站建设 2026/2/7 6:22:35

40、资源管理器功能详解

资源管理器功能详解 1. 资源规范中的空白处理 在资源规范(ResourceSpec)里,名称或冒号前后的空白字符会被忽略。为了让值(Value)能以空白开头,“\space”(反斜杠加空格)会被识别并替换成空格字符,“\tab”(反斜杠加水平制表符)会被识别并替换成水平制表符。若要让…

作者头像 李华