0. 前言
- 用 usbmuxd + lockdown 建立到设备的安全通道,再在其上跑自定义二进制帧协议。
- 照片:分页列举 → 缩略图优先 → 原图/视频按需加载,可取消,避免卡顿。
- URL 自动登录:下沉到 iOS 端构造 scheme 并唤起,前台检测、白名单校验,避免后台态被拒。
- 关键坑:前台限制、scheme 白名单、preload 需重启、配对漂移、大资源需懒加载。
usbmuxd:macOS 系统守护进程(/var/run/usbmuxd 监听 27015),负责把 iOS 设备的 USB 连接“多路复用”为本地 TCP 端口,提供设备发现、端口转发。它不关心应用层协议,只管通道。
lockdown:Apple 官方的设备管理/服务发现协议(基于 plist over TCP),运行在设备上。通过 lockdown 可以:
- 完成配对(pairing),建立信任关系,生成并下发证书与 host id。
- 查询并启动设备内的子服务(如 WebInspector、HouseArrest、AFC、自定义开发者服务)。
- 获取设备信息、会话管理等。
- 二者协作方式:桌面侧先通过 usbmuxd 找到设备、建立 TCP 通道,再用 lockdown 与设备握手/配对,随后请求启动目标服务并获得一个可访问的端口,后续所有业务数据通过该端口传输。
1. 为什么这样做
- 无越狱访问:在官方链路下获取本地照片/视频、唤起自定义 URL。
- 统一体验:桌面侧(Electron + Vue)与设备侧通过协议约定,行为一致且可控。
- 性能体验:缩略图优先、可取消请求,减轻 UI 卡顿。
2. 架构概览
1) 传输层:usbmuxd(多路复用)+ lockdown(配对、启动服务)。
2) 协议层:自定义二进制帧(FrameType:LIST_ASSETS / GET_THUMB / GET_IMAGE / GET_VIDEO / OPEN_URL / AUTO_LOGIN / ERROR)。
3) 桥接层:Electron IPC(preload 暴露、main 注册 handler)。
4) 设备端:iOS Companion(Swift + NWConnection)收帧分发到照片读取、URL 打开、自动登录。
5) 前端:Vue 组件(如 IOSDeviceSection.vue)调 window.electronAPI,实现照片网格、预览灯箱、URL 登录入口
3. usbmuxd + lockdown 工作流
1) 发现设备:向 /var/run/usbmuxd 请求,拿到 udid。
2) StartSession:未配对则 Pair,成功后获取 SessionID。
3) StartService:lockdown 启动目标服务(如自研 URL Service / Photo Proxy),返回设备侧端口。
4) 端口转发:usbmuxd 将本地 TCP 端口连接到设备端口。
5) 业务通信:在该 TCP 上跑自定义帧协议(图片/视频/URL/AUTO_LOGIN)。
4. 关键实现(代码锚点)
- 协议枚举:photo-proxy/protocol/frame.ts
- 桥接收发:photo-proxy/protocol/bridge-port-forward.ts
- URL Service(Node):photo-proxy/usb/url-service.ts
- iOS 端帧分发:photo-proxy/PhotoCompanion/ServiceBinary.swift
- Info.plist 白名单:photo-proxy/PhotoCompanion/Info.plist(如 cardloanTest)
- Electron IPC:electron/preload.cjs、electron/main.js
- 前端集成:src/components/IOSDeviceSection.vue(照片网格、预览、URL 登录入口)
5. 端到端流程示例
5.1 AUTO_LOGIN
- Renderer:setDevice → autoLogin({ phone, env })。
- preload → IPC → main:url-service:auto-login handler 调 URLService。
- Bridge:发 FrameType.AUTO_LOGIN。
- iOS:检查前台 + canOpenURL,UIApplication.shared.open(),回传结果。
- 前端提示:若 app_not_active,提示切前台重试。
5.2 照片预览
- Renderer:listAssets(limit, offset) 分页 → 缩略图 getThumbnail → 预览时再 getImage/getVideoStream。
- Bridge:封帧经 usbmuxd 通道。
- iOS:读相册返回二进制(视频可分块/整段)。
- 前端:懒加载 + AbortController 可取消,下载时落盘再用系统 open 查看。
6. 代码层细节
- 帧格式:type + requestId + payloadLen + payload(JSON),易扩展调试。
- 超时与错误:Bridge 设置超时;设备异常回 FrameType.ERROR;前端统一 toast。
- 前台检查:AUTO_LOGIN 必须 applicationState == .active,否则返回 app_not_active。
- 懒加载/可取消:缩略图优先;预览请求可被 Abort;虚拟滚动减轻 DOM 压力。
- 路径/权限:下载前 mkdir -p;命令执行设置 PATH;处理双引号转义。
7. 调试备忘
- 设备信息:ideviceinfo -s
- 重新配对:idevicepair unpair && idevicepair pair
- 端口转发:iproxy <local> <device>
- 端口占用:lsof -i :<port>
- iOS 日志:log stream --predicate 'process == "PhotoCompanion"'
8. 踩坑与规避
- 后台态唤起失败:必须前台,返回 app_not_active。
- scheme 白名单缺失:canOpenURL 失败,需在 Info.plist 配置。
- preload 未重启:新增 IPC 后必须重启 Electron 主进程。
- 大图/视频卡顿:未做懒加载/可取消;需缩略图优先 + Abort。
- 配对漂移:多机配对导致 HostID 不匹配,必要时重配。
9. FAQ
- 为什么不用 HTTP?usbmuxd/lockdown 给的是裸 TCP,二进制帧轻量可复用、多指令共通道,便于 NWConnection。
- AUTO_LOGIN 为何下沉设备端?桌面侧易受平台/编码/安全策略影响,且后台唤起受限;设备端统一构造并检查前台状态。
- 照片/视频为何分级 + 可取消?避免大资源阻塞 UI,提升滚动与预览流畅度。
- 设备锁屏怎么办?部分服务不可用,需要提示解锁或重试。







Comments | NOTHING