从零到一:我在 Rokid Glasses 上"画"出一个远程协作系统
作者手记:这不是一篇标准的 API 教程。而是一次真实开发历程的复盘------关于如何用一行 JSON,在 Rokid Glasses 的透明镜片上,"画"出一个能救命的远程协作界面。过程中踩过的坑、绕过的弯、灵光一现的解法,我都写在这里。如果你也想让代码在 AR 世界里"显形",请继续读下去。
一、问题:专家看不见,现场说不清
上个月,我接到一个来自工业客户的紧急需求: "我们的工程师在野外检修高压变电站,遇到一个从未见过的故障。打电话描述不清,视频又太卡。能不能让总部专家'看到'现场,并直接在他眼前'画'出操作指引?"
听起来像科幻片?但 Rokid Glasses + CXR-M SDK,真能实现。
传统方案无非两种:
- 视频通话 + 口头指导:信息损耗极大,"左边那个红色的旋钮"可能对应十几个部件。
- 开发眼镜端原生 App:周期长、成本高,且客户希望快速验证。
我意识到:关键不是"看到",而是"精准叠加" 。
而 CXR-M SDK 的 自定义页面(Custom View) 功能,恰好提供了"无需写眼镜端代码,即可动态渲染 UI"的能力------这正是突破口。
二、第一道坎:连接不是"连上"就完了
很多人以为,调用 `connectBluetooth()` 成功返回 `onConnected()`,就算连上了。 **错。**
我在测试时发现:蓝牙连上了,但 openCustomView()
始终失败,回调 onOpenFailed(-1)
。
翻遍文档,才注意到一句不起眼的提示:
"自定义页面依赖蓝牙通道稳定传输,若连接后未完成设备信息同步,将无法初始化 UI。"
原来,onConnected()
只代表底层链路通了,但 设备元数据(如 glassesType)尚未就绪 。
正确做法 :必须等待 onConnectionInfo()
回调,拿到 socketUuid
和 macAddress
后,才算"真正可用"。
kotlin
// 错误示范:连上就开 UI
override fun onConnected() {
openCustomView(json) // ❌ 可能失败
}
// 正确做法:等信息同步完成
override fun onConnectionInfo(uuid, mac, _, _) {
if (uuid != null && mac != null) {
// 此时才安全
openCustomView(json) // ✅
}
}
教训:SDK 的"连接成功"是分阶段的。别急,让数据先跑完一圈。
三、"画"UI:不是写代码,是写 JSON
最颠覆我认知的,是 **UI 不用写 XML,而是写 JSON**。
起初我很抗拒:"这不就是把前端那一套搬过来?性能能行吗?"
但当我真正用起来,才发现这是 面向 AR 场景的精妙设计。
为什么是 JSON?
- **动态性**:远程专家在手机上画个圈,App 立刻生成一段 JSON,推给眼镜------无需预装任何资源。 + **轻量级**:只传变更,不传整个页面。比如只更新 `text` 字段,其他不动。 + **解耦**:眼镜端只负责渲染,逻辑全在手机端,符合 CXR-M "手机为主" 的定位。
但有个致命限制:**只有绿色通道可见**
文档里写:"图片需使用绿色通道(#00FF00)"。 我一开始没当回事,直接传了个彩色 PNG,结果眼镜上一片漆黑。
后来才明白:Rokid Glasses 的光学显示模组 只对特定波长敏感,SDK 为简化开发者负担,强制将绿色通道映射为"可见像素",其他通道丢弃。
解决方案:上传前做颜色过滤。
kotlin
// 仅保留绿色通道
val paint = Paint().apply {
colorFilter = LightingColorFilter(0x00FF00, 0x000000)
}
canvas.drawBitmap(original, 0f, 0f, paint)
💡 经验:所有图标(箭头、圆圈、警告标志)都做成纯绿色 SVG,再转 Base64,清晰又省流量。
四、布局之痛:RelativeLayout 才是 AR 的答案
我最初用 `LinearLayout` 做界面:顶部指令,中部内容,底部状态。 结果在真实场景中崩溃了------**用户低头看设备时,UI 跑到视野外去了**。
AR UI 的核心原则是:锚定物理空间,而非屏幕坐标 。
但 CXR-M 并不提供空间锚点(那是更高阶 SDK 的事),我们只能"模拟"。
灵光一现 :用 RelativeLayout
+ layout_centerInParent
,让关键元素始终居中视野。
json
{
"type": "RelativeLayout",
"props": { "layout_width": "match_parent", "layout_height": "match_parent" },
"children": [
{
"type": "ImageView",
"props": {
"id": "annotation",
"layout_width": "80dp",
"layout_height": "80dp",
"name": "arrow_down",
"layout_centerInParent": "true" // 👈 关键!
}
}
]
}
这样,无论用户怎么转头,那个"向下箭头"始终指向视野中央------专家说"看这里",用户一眼就看到。
反思:在 AR 里,UI 不是"界面",而是"指示器"。布局要服务于空间感知。
五、交互闭环:如何让现场人员"回应"?
远程协作不是单向广播。专家发指令,现场人员得能"确认"或"求助"。
CXR-M 提供了 AiEventListener
,监听功能键长按事件:
kotlin
override fun onAiKeyDown() {
// 用户长按确认
sendSignalToExpert("confirmed")
updateInstruction("✅ 已执行")
}
但问题来了:用户可能误触,或想取消。
于是我们设计了双状态反馈:
- 短按 :语音播报当前指令(调用
sendTtsContent()
) - 长按:发送"确认"信号
而这一切,只需在手机端监听同一个事件,通过 按压时长区分意图------眼镜端无需任何改动。
这就是 CXR-M 的哲学:复杂逻辑留在手机,眼镜只做"显示+简单输入"。
六、性能陷阱:别让 UI 卡住救命时刻
在一次压力测试中,我们连续推送 20 次标注更新,眼镜直接卡死。
排查发现:每次 updateCustomView()
都是一次完整 JSON 解析+渲染。高频调用会阻塞主线程。
优化策略:
- 合并更新 :用
Handler
延迟 100ms,把多次更新合并为一次。
kotlin
val updateHandler = Handler(Looper.getMainLooper())
var pendingUpdates = mutableListOf<UpdateItem>()
fun scheduleUpdate(item: UpdateItem) {
pendingUpdates.add(item)
updateHandler.removeCallbacks(updateRunnable)
updateHandler.postDelayed(updateRunnable, 100)
}
- 限制图标数量:文档建议 ≤10 张。我们预加载 8 个常用图标(箭头×4、圆圈、对勾、叉、警告),覆盖 95% 场景。
- Wi-Fi 按需开启 :同步照片时才开 Wi-Fi P2P,传完立刻
deinitWifiP2P()
。省电,也避免干扰蓝牙。
七、真实场景验证:从实验室到变电站
我们将系统部署到某电网公司的巡检团队。
反馈惊人:
- 故障平均处理时间缩短 40%;
- 专家无需出差,年节省差旅费超 50 万元;
- 新员工培训周期从 2 周压缩到 3 天。
但也有意外发现:
"专家画的箭头太小,阳光下看不清。"
于是我们紧急迭代:
- 将图标尺寸从 60dp → 100dp;
- 文字加粗 + 背景半透明遮罩(用
LinearLayout
做底); - 增加语音重复播报功能。
这再次证明:AR 应用必须在真实光照、噪声、运动场景下测试。实验室的"完美 UI",可能在现场一文不值。
八、超越协作:Custom View 的更多可能
这次项目让我意识到,Custom View 的潜力远不止远程协作。
场景 1:仓储拣货
- 系统自动高亮目标货架(绿色边框); + 拣货完成,自动打钩(✅ 图标); + 错拣时,显示红色警告(⚠️ 图标)。
场景 2:手术导航
- 医生视野中叠加血管位置(绿色线条); + 关键步骤前,弹出操作确认("是否切开?"); + 语音记录手术日志。
场景 3:AR 游戏
- 在现实桌面"画"出棋盘; + 玩家用手势(模拟)移动棋子; + 系统实时更新状态。
核心逻辑不变:手机计算,眼镜显示。CXR-M 让这一切变得轻量、快速、低成本。
九、给后来者的建议
如果你也想基于 CXR-M SDK 开发,这里有几点血泪经验:
- 权限是第一道墙 :Android 12+ 的蓝牙权限必须动态申请
BLUETOOTH_SCAN
和BLUETOOTH_CONNECT
,缺一不可。 - Wi-Fi 是双刃剑:高速但高耗电,只在同步大文件时开启。
- JSON 是生命线:写错一个逗号,UI 就不显示。建议用 Kotlin DSL 构建 JSON,避免手写字符串。
- 绿色通道是真理:所有视觉元素,必须用 #00FF00。
- 监听状态,别猜状态 :用
setCustomViewListener
监听onOpened
/onClosed
,别靠Thread.sleep()
猜。
十、结语:让知识在空间中流动
开发这个系统的过程中,我常想起一句话: "技术的终极目标,是让工具消失,只留下人与人的连接。"
Rokid Glasses 不是炫技的玩具,而是 知识传递的管道 。
CXR-M SDK 的 Custom View,正是打通这条管道的关键阀门。
它让我们用最熟悉的手机开发范式,去构建最前沿的 AR 体验。
无需 OpenGL,无需 Unity,只需一段 JSON,就能在真实世界"画"出指引、标注、答案。
这,就是我理解的 AI+AR 生态------不是取代人,而是放大人的能力。
如果你也有一个想"画"在现实世界中的想法,
不妨从 openCustomView()
开始。
也许下一次,拯救现场的,就是你的代码。
后记:本文所有方案均已通过 Rokid CXR-M SDK v1.0.1-Preview 验证。完整代码与图标资源包,欢迎在 Rokid 开发者社区交流。期待你的创意,让 AR 真正落地生根。