【免费下载链接】airsync-mac
AirSync macOS - Bring the forbidden macOS continuity to Android
本文深入解析AirSync 协议:基于一条 WebSocket 长连接与 23 种 JSON 消息类型,带你从零构建自己的 AirSync 客户端——理解 Android 与 Mac 之间的通知镜像、文件传输、媒体控制等核心通信机制,以及 AES-GCM 加密与心跳保活的完整实现。
1. AirSync 是什么?
AirSync是一款开源的 macOS 应用,目标是把 Apple "Continuity(接力)" 体验带到 Android 手机:手机通知同步到 Mac 桌面、音乐状态镜像到控制中心、Mac 反向控制手机媒体、双端文件互传、剪贴板同步等。
它的通信核心并不复杂:
- Mac 端运行一个WebSocket Server(基于 Swifter 库)
- Android 端作为WebSocket Client主动连接
- 所有业务数据都封装成统一的 JSON 消息,走 AES-GCM 加密通道
💡 如果你想在本地编译源码,可以克隆仓库:
git clone https://gitcode.com/gh_mirrors/ai/airsync-mac
2. 协议总览:一条连接 + 一个信封
整个协议可以浓缩成两句话:连上ws://<Mac的IP>:6996/socket,然后收发{"type": "消息类型", "data": {...}}这样的 JSON 信封。
| 关键参数 | 值 | 说明 |
|---|---|---|
| 默认端口 | 6996 | 定义于 Constants.swift,可在设置页修改 |
| WebSocket 端点 | /socket | 注册于 WebSocketServer.swift |
| 消息信封 | {"type": "...", "data": {...}} | 全部消息的统一结构 |
| 传输加密 | AES-GCM (256-bit) | 密文 Base64 后作为文本帧发送 |
| 心跳间隔 | 4.5 秒 | 无活动 20 秒判定会话失效 |
消息信封由两个字段组成,类型枚举完整定义在 Message.swift,data字段采用"万能解码器"策略(见 AnyDecodable.swift),允许各消息类型自由携带不同结构的 JSON 对象:
{ "type": "status", "data": { "battery": { "level": 87, "isCharging": true }, "isPaired": true } }3. 第一步:发现 Mac(Bonjour 广播)
你的客户端首先需要"找到"Mac。AirSync 使用Bonjour(mDNS)做局域网服务发现,实现在 BonjourServiceAdvertiser.swift:
| 发现项 | 内容 |
|---|---|
| 服务类型 | _airsync._tcp |
| 服务名 | AirSync-<设备名> |
| TXT 记录 | id(稳定 UUID)、name、port、ver |
在 macOS 或任意支持 mDNS 浏览的工具中搜索_airsync._tcp即可定位到 Mac 的 IP 与端口,随后直接发起 WebSocket 连接。此外项目还内置了 UDP 广播(UdpBroadcastDiscovery.swift)作为补充发现手段。
4. 握手:device与macInfo消息对
连接建立后,第一条消息必须是device类型的手shake,完成设备注册。核心字段如下:
{ "type": "device", "data": { "name": "Pixel 9", "id": "12345abc", "ipAddress": "192.168.1.50", "port": 8765, "version": "2.0.0", "adbPorts": ["5555"], "wallpaper": "base64...", "bleAuthToken": "token...", "targetIpAddress": "192.168.1.20" } }Mac 收到后会回一条macInfo消息,携带 Mac 的名称、机型(modelIdentifier)、版本、是否 Plus 订阅以及已保存的 Android 应用包列表(见 WebSocketServer+Handlers.swift)。
⚠️主会话机制:Mac 只把第一个连接视为primarySession,除device外的所有消息只接受来自主会话(WebSocketServer+Handlers.swift)。若新的本地网段会话(192.168./10./172.16-31)发起握手,会强制接管并断开旧会话——这是为了"局域网优先"的稳定性设计。
5. 23 种 JSON 消息类型全解
当前源码的MessageType枚举共定义了 31 个值,其中消息路由器主动分发处理 23 种(handleMessage 的 switch 分支),其余 8 种主要是 Mac 主动发给 Android 的出站类型。按功能分组如下:
🔌 连接与状态(3 种)
| 类型 | 方向 | 核心 data 字段 |
|---|---|---|
device | Android→Mac | name / id / ipAddress / port / version / wallpaper |
status | 双向 | battery / isPaired / music(播放状态、专辑封面、进度) |
userDisconnected | Android→Mac | 用户主动断开前发送,Mac 据此走"手动断开"流程 |
🔔 通知镜像(5 种)
| 类型 | 方向 | 作用 |
|---|---|---|
notification | Android→Mac | 推送通知:id / title / body / app / package / priority / actions / progress / ongoing |
notificationAction | Mac→Android | 在 Mac 上点通知按钮,转发操作(如"回复") |
notificationActionResponse | Android→Mac | 操作执行结果(id / action / success) |
notificationUpdate | Android→Mac | 通知内容更新或消失(action=dismiss) |
dismissalResponse | Android→Mac | 取消通知的结果回执 |
🎵 媒体与通话(7 种)
| 类型 | 方向 | 作用 |
|---|---|---|
macMediaControl | Android→Mac | 用 Android 控制 Mac 的音乐:play / pause / next / previous / stop / playPause |
macMediaControlResponse | Mac→Android | Mac 侧执行结果回执 |
mediaControlResponse | Android→Mac | Android 侧媒体操作结果回执 |
callEvent | Android→Mac | 来电/去电事件:eventId / number / direction / state / contactName |
callProgress | Android→Mac | 通话中进度更新(如振铃转接通) |
callControl | Mac→Android | Mac 上接听/挂断:accept / decline / end |
callControlResponse | Android→Mac | 通话控制结果(action / success / message) |
📋 应用与剪贴板(2 种)
| 类型 | 方向 | 作用 |
|---|---|---|
appIcons | Android→Mac | 同步应用列表:{包名: {name, icon(base64), systemApp, listening}},Mac 会落盘缓存图标 |
clipboardUpdate | Android→Mac | 剪贴板文本同步(text 字段) |
📁 文件传输(6 种)
| 类型 | 方向 | 作用 |
|---|---|---|
fileTransferInit | 双向 | 开始传输:id / name / size / mime / chunkSize / checksum / isClipboard |
fileChunk | 双向 | 数据分片:id / index / chunk(base64) |
fileChunkAck | 双向 | 分片确认:id / index |
fileTransferComplete | 双向 | 传输结束,触发大小与 SHA256 校验 |
transferVerified | Android→Mac | 接收方校验结果(verified 布尔值) |
fileTransferCancel | 双向 | 取消本次传输(id) |
消息模板的构造逻辑集中在 FileTransferProtocol.swift。
🖱️ 远程控制与文件浏览(6 种)
| 类型 | 方向 | 作用 |
|---|---|---|
remoteControl | Android→Mac | 用 Android 遥控 Mac:键盘(keypress / type)、鼠标(mouse_move / mouse_click / mouse_scroll)、音量、锁屏、屏保、亮度等 20+ 动作 |
browseLs | Mac→Android | 请求列出 Android 目录(path / showHidden) |
browseData | Android→Mac | 返回目录项列表:items: [{name, isDir, size, time}],出错时返回error |
remoteControl的完整动作表可参考 handleRemoteControl。
📤 其余 8 种:Mac 出站的"另一半协议"
枚举中剩余的值——macInfo、volumeControl、macVolume、toggleAppNotif、wakeUpRequest、macMediaControlResponse等——主要由 Mac 发出(如volumeControl让 Android 调音量、browseLs拉取目录),以及少量双向复用类型。客户端实现时只需把 Mac 端 WebSocketServer+Outgoing.swift 里的sendMessage调用当作"待实现消息清单"逐一对照即可。
6. 加密层:AES-GCM 如何工作
协议的所有消息默认经过AES-GCM 256-bit加密(CryptoUtil.swift):
- Mac 首次启动时随机生成 256-bit 密钥,Base64 后持久化保存
- 发送时:
AES.GCM.seal→ 得到nonce(12B) + 密文 + tag(16B)的组合字节 → Base64 → 作为文本帧发送 - 接收时:Base64 解码 →
AES.GCM.open还原出 JSON 明文
调试技巧:Mac 端在未加载密钥时会以明文收发消息(WebSocketServer.swift),开发阶段可以先用明文打通流程,再接加密。
7. 心跳机制:4.5 秒 Ping,20 秒超时
保活逻辑在 WebSocketServer+Ping.swift:
- Mac 每4.5 秒发送
{"type":"ping","data":{}} - 客户端应回一条
{"type":"status","data":{"type":"pong"}}(Mac 收到后直接忽略,但任何入站文本帧都会刷新活跃时间戳) - 超过12 秒无活动 → 标记"弱连接"(UI 提示)
- 超过20 秒无活动 → 判定会话失效,自动重启服务并重新广播 Bonjour 发现,Android 会重新发现并连接
这意味着你的客户端只要定期"说话"(哪怕回一个空消息),连接就能一直保持。
8. 文件传输协议:滑动窗口 + 校验
文件传输是协议中最精巧的部分(sendFile),流程为:
Init(id, name, size, mime, chunkSize, checksum) → Chunk(index 0) → Ack(0) → Chunk(index 1) → Ack(1) ← 滑动窗口大小 = 8 → ... → Complete(id, size, checksum) → Verified(id, verified: true)| 参数 | 默认值 | 说明 |
|---|---|---|
| chunkSize | 64 KB | 每个分片的字节数 |
| 窗口大小 | 8 | 最多 8 个分片"在途",收到 Ack 后推进 |
| Ack 超时 | 2000 ms | 超时重发,最多重试 3 次 |
| 完整性 | SHA256 | Complete 时校验,落盘到 Downloads 并自动去重重命名 |
接收端(Mac 侧)按index * chunkSize偏移写入临时文件(handleFileChunk),所以分片乱序到达也没关系。
9. 从零构建你的客户端:5 步清单
✅第 1 步|建立连接:通过 Bonjour 找到_airsync._tcp服务,连接ws://<IP>:6996/socket
✅第 2 步|完成握手:发送device消息(name / id / ipAddress / port 必填),等待macInfo回复
✅第 3 步|保持心跳:收到ping后回status/pong,并每几秒主动推送一次status(电量 + 音乐状态)
✅第 4 步|订阅核心事件:处理notification(渲染通知卡片)、callEvent(来电弹窗)、appIcons(应用网格)
✅第 5 步|实现文件传输:按 Init → Chunk/Ack → Complete → Verified 四步状态机收发文件,记得做 SHA256 校验
一个最简的"能跑起来"的客户端,实际上只需要正确处理device、status、ping三类消息,Mac 端就会认为配对成功并进入主页。
10. 常见坑与调试建议
| 坑 | 原因与解决 |
|---|---|
| 消息发了没反应 | 你不在主会话上——非 primary 会话除device外的消息一律被丢弃,先确认连接顺序 |
| 数字精度丢失 | JSON 数字可能被解析为 Int/Double 混排(如音乐的 duration/position 以毫秒计的整型),建议按数值统一处理 |
| 进度条"漂移" | status.music.position是快照值,协议携带positionTimestamp做类 NTP 的时间补偿,播放中应自行累加 |
| 连接突然重启 | Mac 检测到 IP 变化(切换 WiFi/热点)会 5 秒后重启服务并刷新二维码,客户端要能自动重连 |
| 大文件传输卡住 | 确认按 index 顺序推进窗口、及时回 Ack,2 秒超时会触发重发 |
11. 源码文件速查
| 模块 | 路径 |
|---|---|
| 消息类型枚举与信封定义 | Message.swift |
| WebSocket 服务主体(路由/密钥/重启) | WebSocketServer.swift |
| 23 种消息的分发处理 | WebSocketServer+Handlers.swift |
| 出站消息与文件发送 | WebSocketServer+Outgoing.swift |
| Ping 心跳与超时 | WebSocketServer+Ping.swift |
| 网络适配与 IP 变更监控 | WebSocketServer+Networking.swift |
| AES-GCM 加解密 | CryptoUtil.swift |
| 文件传输消息模板 | FileTransferProtocol.swift |
| Bonjour 服务广播 | BonjourServiceAdvertiser.swift |
| 默认端口等常量 | Constants.swift |
结语
AirSync 协议的精髓在于简单:一条 WebSocket、一种 JSON 信封、23 种主动处理的消息类型、一套 AES-GCM 加密和 4.5 秒心跳,就支撑起了通知镜像、来电提醒、文件互传、远程桌面控制等完整的跨设备体验。理解了device握手、status心跳与文件四步状态机这三块骨架,你的自定义客户端就已经具备接入 AirSync 生态的全部能力——剩下的,只是挑你想要的那些消息类型去实现而已。
【免费下载链接】airsync-mac
AirSync macOS - Bring the forbidden macOS continuity to Android
相关推荐
从零构建 Taskwarrior Taskserver 客户端:凭证、同步协议与消息格式权威指南
从零构建 Taskwarrior Taskserver 客户端:凭证、同步协议与消息格式权威指南 导读 本文以 Taskwarrior 官方 RFC 文档 do
开发工具Ivy核心功能揭秘:EDR免Hook与ETW补丁技术原理详解
Ivy核心功能揭秘:EDR免Hook与ETW补丁技术原理详解 Ivy作为一款强大的内存中VBA代码执行框架,其核心功能在于能够绕过现代端点检测与响应(EDR)系
Tutanota开发者指南:如何从源码构建自己的邮件客户端
Tutanota开发者指南:如何从源码构建自己的邮件客户端 Tutanota是一款专注于安全隐私的端到端加密邮件服务,让你在所有设备上都能加密邮件、联系人及日历
即时通讯密码学前端移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考