Android 数据信息传输解析

第一章:Android 消息机制

1.1 消息机制概述

Android 消息机制由 Handler、Looper 和 MessageQueue 三者协作完成,用于在单线程内实现异步任务调度(如主线程更新 UI)。该机制可分为四个过程:

  1. 消息机制初始化
  2. 消息轮询
  3. 消息发送
  4. 消息处理

其底层基于 Linux 的 epoll(I/O 事件通知机制)eventfd(事件通知文件描述符) 实现。

【补充】可将整个消息机制想象成“邮局系统”:

  • Message = 信件
  • MessageQueue = 邮筒(按投递时间排序)
  • Looper = 邮递员(不断检查邮筒)
  • Handler = 寄件人 & 收件人
  • epoll/eventfd = 邮局的“有信提醒铃”

1.2 消息机制初始化

  • Looper.prepare() 开始(通常在主线程自动调用);
  • 调用 Looper 构造函数,创建 Looper 实例,并存入线程私有变量 ThreadLocal
  • Looper 构造函数中创建 MessageQueue
  • MessageQueue 构造方法调用 nativeInit()(JNI 方法),初始化 Native 层消息队列;
  • Native 层 MessageQueue 创建时,调用 Native Looper 构造函数;
  • Native Looper 构造函数调用 rebuildEpollLocked()
  • rebuildEpollLocked() 中:
    • 调用 epoll_create1() 创建 epoll 实例(返回一个文件描述符);
    • 调用 epoll_ctl()唤醒事件文件描述符(eventfd) 注册到 epoll 监听列表。

【补充】epoll vs select/poll

  • select/poll 每次都要遍历所有监听的文件描述符,效率随数量下降;
  • epoll 将“注册”和“等待”分离,内部用红黑树管理,性能稳定。
  • epoll 实例内部包含两个关键结构:
    • rbr(红黑树根节点):存储所有被监控的事件(高效插入/查找);
    • rdlist(就绪链表):当事件发生时,内核将其加入此链表,供 epoll_wait() 返回。

1.3 eventfd 唤醒机制

  • 唤醒事件文件描述符为 eventfd 对象(Linux 特有);
  • 相比传统 pipeeventfd 支持 广播式通知(多个线程可同时 wait,write 一次全部唤醒),而 pipe 仅支持点对点;
  • eventfd_ctx 结构体包含:
    • wqh:等待队列头结点(带自旋锁的双向链表,用于挂起等待线程);
    • count:64 位无符号整数计数器(每次 write 增加,read 读取并清零)。

【补充】eventfd 就像一个“全局计数器铃”:

  • 写入一次(如写 8 字节的 1),计数器 +1;
  • 所有在 epoll_wait 中等待该 fd 的线程都会被唤醒。

1.4 消息轮询过程

  • Looper.loop() 开始(进入死循环);
  • loop() 调用 MessageQueue.next() 获取下一条待处理消息;
  • next() 调用 nativePollOnce(timeout)(JNI 方法)
  • 调用链深入 Native 层:
    nativePollOnce()NativeMessageQueue.pollOnce()Native Looper.pollOnce()
  • 最终调用 epoll_wait(timeout)
    • 若消息队列为空且无延时消息timeout = -1,线程进入永久阻塞状态
    • 关键点:此阻塞由内核管理,不消耗 CPU,因此 Looper 死循环不会导致 ANR;
  • epoll_wait 返回后(因新消息到达或超时),MessageQueue 检查消息类型:
    • 若遇到同步屏障(Sync Barrier),则跳过所有同步消息,优先返回异步消息(如 UI 绘制 invalidate);
    • 否则返回下一条已到达发送时间的非异步消息

【补充】同步屏障是 Android 保证 UI 流畅的关键机制:
当系统需要立即绘制(如点击按钮),会插入一个“屏障”,临时屏蔽普通消息,让绘制消息插队执行。

1.5 消息发送过程

  • Handler.sendMessage()post(Runnable) 开始;
  • Handler 调用 MessageQueue.enqueueMessage(msg, when) 将消息按时间顺序插入队列;
  • MessageQueue 本质是单链表(非环形队列),按 when(触发时间戳)排序;
  • enqueueMessage() 逻辑:
    1. msg.when >= 链表尾节点.when,直接插入尾部;
    2. 否则遍历链表找到插入位置;
    3. 判断是否需要唤醒轮询线程:
      • 需要唤醒条件:新消息是队列首条,或为异步消息且存在同步屏障;
      • 若需唤醒,调用 nativeWake()(JNI 方法)
      • nativeWake()NativeMessageQueue.wake()Native Looper.wake()
      • Native Looper.wake() 通过 write(eventfd, &W, sizeof(W)) 向 eventfd 写入一个值;
      • 内核检测到 eventfd 可读,立即唤醒阻塞在 epoll_wait 的线程

1.6 消息处理过程

  • Looper.loop()next() 获取消息后,调用 msg.target.dispatchMessage(msg)
  • msg.target 即发送消息时的 Handler 实例
  • Handler.dispatchMessage() 执行顺序:
    1. msg.callback != null(如通过 post(Runnable) 发送),则执行 msg.callback.run()
    2. 否则调用 handleMessage(msg)(开发者重写的业务逻辑)。

1.7 最佳实践与常见问题

  • Message 复用:务必使用 Message.obtain() 从全局池获取对象,避免频繁创建 GC;
  • IdleHandler:通过 Looper.myQueue().addIdleHandler() 添加,用于主线程空闲时执行轻量任务(如预加载);
  • Handler 内存泄漏
    • 非静态内部 Handler 会隐式持有外部 Activity 引用;
    • 若消息未及时处理,Activity 无法回收 → 内存泄漏;
    • 解决方案
      • 使用 静态内部 class + WeakReference
      • 或在 Activity.onDestroy() 中调用 handler.removeCallbacksAndMessages(null)
  • ANR 风险handleMessage() 在主线程执行,耗时操作会导致 ANR
  • 异步消息优先级
    • 系统可通过 Handler(async=true) 创建异步 Handler(应用层不可用);
    • 异步消息在分发时优先于同步消息
    • 同步屏障由系统在 ViewRootImpl.scheduleTraversals() 中插入,确保 UI 绘制不被普通消息阻塞。

【补充】消息处理顺序(Native 层):

  1. 处理 Native Message(如 Input 事件)
  2. 处理 Native Request(如 Choreographer 回调)
  3. 处理 Java Message(Handler 消息)
    这解释了为何“Java 层消息少,但 UI 仍卡顿”——底层事件可能积压。

📚 深入 Handler 机制:Android 消息机制
📚 Handler 原理详解:Handler消息机制


第二章:进程间通信(IPC)机制

2.1 Linux 进程通信方式

  • 管道(Pipe):单向、1v1、数据一次性读取;
  • 共享内存:双向、最快(无需拷贝)、无同步机制、多对多;
  • Socket:基于文件接口,双向、多对多、需两次数据拷贝、支持跨主机。

2.2 Android IPC 方式:Binder

  • 内核空间:操作系统独占的内存区域(全系统仅一份);
  • 用户空间:每个进程独立的虚拟内存区域(多份);
  • 虚拟内存:程序使用虚拟地址编程,由 MMU(Memory Management Unit,内存管理单元) 硬件将虚拟地址翻译为物理地址;
    • MMU 是硬件芯片,非软件模块;
    • 可设置内存页的读写/执行权限,实现进程隔离与安全。

2.3 虚拟内存管理

  • 虚拟内存划分为固定大小块 —— 页(Page),默认 4096 字节(4KB)
  • 物理内存划分为 页框(Page Frame),同样 4KB;
  • 页表(Page Table):存储虚拟页号 ↔ 物理页框号的映射关系,由 MMU 使用。
MMU-内存管理单元

2.4 Binder 通信原理

  • 传统 IPC(如 Socket)需 两次数据拷贝
    进程A用户空间 → 内核缓冲区 → 进程B用户空间
  • Binder 通过 mmap 减少一次拷贝
    1. Binder 驱动在内核创建一块 共享内存缓冲区
    2. 发送方进程通过 mmap() 将该缓冲区映射到自己的用户空间;
    3. 发送方直接写入映射地址 → 数据进入内核缓冲区(一次拷贝);
    4. 接收方通过 Binder 驱动直接访问同一内核缓冲区零拷贝)。
进程通讯

📚 深入 Binder 机制:Android 大话binder通信

2.5 mmap 机制详解

  • mmap(memory map) 允许程序将文件或设备直接映射到虚拟内存,避免 read/write 系统调用
  • 适用场景:高性能 I/O(如数据库、图形处理);
  • 限制
    • 映射大小必须是 页面(4KB)整数倍
    • 面向流的设备(如串口)不支持 mmap;
    • 具体实现依赖硬件平台。
  • 关键特性
    • mmap 仅分配虚拟地址空间不立即加载文件内容
    • 首次访问虚拟地址时触发 缺页异常(Page Fault)
    • 内核以 页为单位 从磁盘加载数据到物理内存;
    • 操作系统可能预读相邻页,提升后续访问速度。
MMAP

📚 mmap 原理解析:mmap的解析


第三章:AIDL 与跨进程通信

3.1 AIDL 基础

  • AIDL(Android Interface Definition Language) 用于定义跨进程接口;
  • 支持的数据类型
    • 8 种基础类型(int, long, boolean 等);
    • String、CharSequence;
    • Parcelable(需实现序列化);
    • List、Map(元素类型也需支持 AIDL)。

【补充】AIDL 文件本身不执行任何逻辑,它只是一个接口契约模板。编译时,AAPT 会根据它生成 Java 接口和 Stub/Proxy 类。

📚 AIDL 应用示例:AIDL简单应用

3.2 AIDL 工作原理

  • 真正起作用的不是 .aidl 文件,而是系统生成的 Java 代码
  • 服务端创建一个 Service,并在 onBind() 中返回 Stub 的实现类
  • 客户端通过 bindService() 获取 Proxy 对象
  • 客户端调用 Proxy 方法 → 通过 Binder 驱动 跨进程调用服务端 Stub 方法;
  • 本质:AIDL 是 Binder 通信的语法糖,简化了手动编写 IBinder 接口的工作。

【补充】可将 AIDL 理解为“远程遥控器”:

  • 你在客户端按“开机”(调用方法);
  • 信号通过红外(Binder)传到电视(服务端);
  • 电视执行开机逻辑,并返回状态。

📚 深入 AIDL:AIDL


第四章:Intent 数据传递限制

4.1 TransactionTooLargeException

  • 使用 Intent 传递数据时,可能抛出 TransactionTooLargeException
  • 根本原因:Intent 携带的数据需通过 Binder 传输,而 Binder 有全局缓冲区大小限制
  • 默认上限:约 1MB(实际为 MAX_TRANSACTION_SIZE = 1 << 20 字节);
  • 关键注意
    • 此 1MB 是整个进程所有 Binder 事务共享的缓冲区,非单次 Intent 限制;
    • 不同 Android 版本或厂商 ROM 可能调整此值;
    • 即使数据 < 1MB,若系统繁忙也可能触发异常。

【补充】最佳实践

  • 避免通过 Intent 传递 Bitmap、大 List 等;
  • 改用 全局单例、文件路径、ContentProvider 或 EventBus 传递大数据。

📚 详情:Intent传递数据大小限制


第五章:Android 四大组件与进程模型

5.1 四大组件特性

  • 均可跨进程通信(通过 Intent + Binder);
  • Activity vs Service
    • Activity 是有状态的,状态通过 savedInstanceState 表达;
    • Service 无 UI,无状态(除非自行维护)。

5.2 Application 与进程生命周期

  • 每个进程有且仅有一个 Application 对象
  • 进程启动时,系统依次调用:
    1. attachBaseContext()(可在此替换 Context)
    2. 注册所有静态 ContentProvider
    3. Application.onCreate()
  • 进程存活条件:进程中存在至少一个活跃组件(Activity/Service/BroadcastReceiver/ContentProvider);
  • 进程销毁:当所有组件 inactive 且系统内存不足时,系统杀死进程;
  • 进程重建:若因 crash 或 System.exit() 终止,系统会重建进程并重启最后活跃的 Activity

📚 Binder IPC 原理:Android IPC原理
📚 跨进程设计案例:shadow的跨进程设计


第六章:BroadcastReceiver 与 Binder 服务

6.1 BroadcastReceiver 原理

  • 广播接收机制完全基于 Binder 实现;
  • 系统服务 ActivityManagerService (AMS) 维护一个全局的 广播注册表
  • 静态广播(AndroidManifest 注册):
    • 即使进程未启动,AMS 也能通过 PMS 启动目标进程;
    • 适用于开机启动、网络变化等系统级事件。
  • 动态广播(代码 registerReceiver):
    • 仅在注册的进程/组件存活时有效;
    • 生命周期与注册上下文绑定。

【补充】广播就像“全村喇叭”:

  • 系统(村长)喊一声“断网了!”;
  • 所有登记过的村民(Receiver)都能听到并行动。

📚 深入分析:BroadcastReceiver广播原理分析

6.2 Binder 核心接口

  • IBinder:跨进程通信的基础接口,定义了 transact() 方法;
  • Parcel:高性能数据容器,用于在 Binder 事务中序列化/反序列化数据;
  • BinderIBinder 的具体实现类,服务端继承它提供功能;
  • BinderProxy:客户端持有的代理对象,封装了 Binder 驱动调用。

📚 机制详解:Android跨进程通讯机制之Binder、IBinder、Parcel、AIDL
📚 Binder 传递细节:Binder机制

Binder IPC通讯流程.png

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

相关阅读更多精彩内容

友情链接更多精彩内容