从社死边缘拯救我:用 AR 眼镜打造“亲戚称呼助手“
从社死边缘拯救我用 AR 眼镜打造亲戚称呼助手一个真实的新年灾难大年初二我跟着新婚妻子回娘家。刚进门七大姑八大姨就围了上来。一位头发花白的阿姨笑盈盈地递过来一个红包我脑子里嗡的一声——这到底是妻子的哪位亲戚大姨小姨还是什么远房表姑“小张啊还认识我不”我支支吾吾半天最后还是妻子打了圆场“这是大姨小时候还抱过你呢”那一刻我看到了大姨眼里的失望。这种社死现场相信很多人都经历过春节期间走亲访友是必修课但那些一年见一次的亲戚名字和称呼根本记不住。尤其是刚结婚的新人、不常回家的打工人简直是称呼灾难高发人群。回家后我下定决心明年春节我绝不能再叫错人。思路为什么是 AR 眼镜解决方案无非几种● 记在手机备忘录掏手机、解锁、搜索太慢而且当着亲戚面查手机很不礼貌● 记在小本本上更尴尬像是在做作弊小抄● 让家人提醒每次都要麻烦别人不靠谱想了很久我注意到桌上的 Rokid AR 眼镜。眼镜有几个天然优势对比维度手机AR 眼镜使用隐蔽性众人可见你在查手机只有自己能看到屏幕内容操作便捷度掏出→解锁→搜索→查看抬眼即见无需动手社交压力明显在看手机不礼貌自然地瞟一眼谁也发现不了响应速度打开APP需要几秒信息即时显示项目搭建从零开始集成 SDK1. 创建项目并配置 Maven 仓库首先是一个标准的 Android 项目Kotlin 语言minSdk 设为 28SDK 硬性要求。在 settings.gradle.kts 中添加 Rokid 的 Maven 仓库// settings.gradle.kts dependencyResolutionManagement{repositories{maven{urluri(https://maven.rokid.com/repository/maven-public/)}google()mavenCentral()}}2. 添加依赖项在 app/build.gradle.kts 中引入 CXR-M SDK 和必要的 Android 组件// app/build.gradle.kts android{namespacecom.rokid.relativehelpercompileSdk34defaultConfig{minSdk28targetSdk34}}dependencies{// Rokid CXR-M SDK implementation(com.rokid.cxr:client-m:1.0.1-20250812.080117-2)// Android 组件 implementation(androidx.core:core-ktx:1.12.0)implementation(com.google.android.material:material:1.11.0)implementation(androidx.recyclerview:recyclerview:1.3.2)implementation(androidx.lifecycle:lifecycle-livedata-ktx:2.7.0)}3. 权限声明眼镜与手机通过蓝牙通信需要声明相关权限。Android 12 对蓝牙权限做了拆分需要特别注意!-- AndroidManifest.xml --!-- 蓝牙基础权限 --uses-permission android:nameandroid.permission.BLUETOOTH/uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN/!-- Android12 蓝牙扫描/连接权限 --uses-permission android:nameandroid.permission.BLUETOOTH_SCANandroid:usesPermissionFlagsneverForLocation/uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT/!-- 定位权限部分设备蓝牙扫描需要 --uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION/关键提示Android 12 以上BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT 必须在运行时动态申请仅 Manifest 声明会导致崩溃。数据模型如何存储亲戚信息先定义一个简洁的数据结构。每个亲戚条目需要名字、称呼、关系描述、拜年话术以及可选的拼音和备注// model/Relative.kt data class Relative(var id: Long0, val name: String, // 亲戚名字如王芳val title: String, // 称呼如大姨、叔叔val relation: String, // 关系描述如妈妈的姐姐val greetingTemplate: String, // 拜年话术模板 val phonetic: String?null, // 名字拼音可选防止读错 val notes: String?null // 备注可选){/** * 生成眼镜端显示的格式化文本 */ fun toGlassDisplayText(): StringbuildString{appendLine($name)appendLine()appendLine(称呼$title)appendLine(关系$relation)appendLine()appendLine(────── 拜年话术 ──────)appendLine(greetingTemplate)}}这个 toGlassDisplayText() 方法很关键——它决定了信息在眼镜上的呈现方式。我选择了简洁清晰的格式 王芳 称呼大姨 关系妈妈的姐姐 ────── 拜年话术 ────── 大姨新年好祝您身体健康万事如意这样在眼镜上一眼就能看到关键信息。数据持久化亲戚数据需要持久化存储。考虑到数据量不大通常几十条我选择了最简单的 SharedPreferences JSON 序列化方案// data/RelativeRepository.kt object RelativeRepository{private const val PREFS_NAMErelative_helper_prefsprivate const val KEY_RELATIVESrelativesprivate val relativesmutableListOfRelative()fun init(context: Context){loadFromPrefs(context)if(relatives.isEmpty()){loadPresetData()// 首次运行加载预设数据}}fun searchRelatives(keyword: String): ListRelative{if(keyword.isBlank())returnrelatives.toList()val lowerKeywordkeyword.lowercase()returnrelatives.filter{it.name.contains(keyword, ignoreCasetrue)||it.title.contains(keyword, ignoreCasetrue)||it.relation.contains(keyword, ignoreCasetrue)}}private funloadPresetData(){// 内置20条常见亲戚数据涵盖祖辈、父辈、同辈 relatives.addAll(listOf(Relative(1,王芳,大姨,妈妈的姐姐,大姨新年好祝您身体健康万事如意), Relative(2,李明,叔叔,爸爸的弟弟,叔叔过年好祝您事业顺利财源广进), //... 更多预设))}}此外我还实现了一个贴心的小功能根据称呼自动生成拜年话术。比如输入爷爷自动填充爷爷新年好祝您身体健康长命百岁fun generateDefaultGreeting(title: String): Stringwhen(title){爷爷,外公-${title}新年好祝您身体健康长命百岁奶奶,外婆-${title}新年好祝您福如东海寿比南山姑姑,婶婶,舅妈,大姨,小姨-${title}新年好祝您青春永驻越来越年轻表哥,堂哥-${title}新年好祝今年发大财else-${title}新年好祝您新年快乐万事如意}核心眼镜通信模块这是整个项目最核心的部分——如何让手机和眼镜对话SDK 封装思路CXR-M SDK 提供了 CxrApi 类作为通信入口包含蓝牙连接、场景控制、数据发送等功能。为了方便使用我将其封装成单例对象 RokidGlassesManager// sdk/RokidGlassesManager.kt object RokidGlassesManager{private val cxrApi: CxrApi by lazy{CxrApi.getInstance()}private var connectionCallback: ConnectionCallback?null // 连接状态回调接口 interface ConnectionCallback{fun onConnecting()fun onConnected()fun onDisconnected()fun onFailed(errorMsg: String)}// 检查蓝牙是否已连接 val isBluetoothConnected: Boolean get()cxrApi.isBluetoothConnected}蓝牙连接流程连接眼镜分为两步先调用 initBluetooth 获取连接信息再调用 connectBluetooth 建立实际连接fun initBluetoothConnection(context: Context, device: BluetoothDevice){connectionCallback?.onConnecting()// 第一步初始化蓝牙获取 UUID 和 MAC 地址 cxrApi.initBluetooth(contextcontext, devicedevice, callbackobject:BluetoothStatusCallback(){override fun onConnectionInfo(socketUuid: String?, macAddress: String?, rokidAccount: String?, glassesType: Int){if(!socketUuid.isNullOrEmpty()!macAddress.isNullOrEmpty()){// 第二步使用获取的信息建立连接 connectBluetooth(context, socketUuid, macAddress)}else{connectionCallback?.onFailed(获取连接信息失败)}}override funonConnected(){connectionCallback?.onConnected()}override funonDisconnected(){connectionCallback?.onDisconnected()}override fun onFailed(errorCode: ValueUtil.CxrBluetoothErrorCode?){connectionCallback?.onFailed(getBluetoothErrorMessage(errorCode))}})}踩坑提醒SDK 的蓝牙连接是异步的所有结果都通过回调返回。不要试图同步等待连接结果会导致死锁。发送数据到眼镜数据发送是整个应用的关键功能。这里有一个必须注意的顺序先打开提词器场景再发送文本数据fun sendTextToGlasses(text: String, callback: SendCallback?null): Boolean{if(!isBluetoothConnected){callback?.onFailed(眼镜未连接)returnfalse}// 关键必须先打开提词器场景 openWordTipsScene()val statuscxrApi.sendStream(typeValueUtil.CxrStreamType.WORD_TIPS, streamtext.toByteArray(Charsets.UTF_8), // 注意编码 fileNamerelative_info.txt, cbobject:SendStatusCallback(){override funonSendSucceed(){callback?.onSuccess()}override fun onSendFailed(errorCode: ValueUtil.CxrSendErrorCode?){callback?.onFailed(getSendErrorMessage(errorCode))}})returnstatusValueUtil.CxrStatus.REQUEST_SUCCEED}private fun openWordTipsScene(): Boolean{val statuscxrApi.controlScene(sceneTypeValueUtil.CxrSceneType.WORD_TIPS, openOrClosetrue, otherParamsnull)returnstatusValueUtil.CxrStatus.REQUEST_SUCCEED}我第一次调试时sendStream 返回成功但眼镜端什么都没显示。排查了半天才发现是场景没打开——这个坑踩得很痛。TTS 语音播报除了文字显示SDK 还支持 TTS文字转语音功能可以在同步信息后播放语音提示fun sendTtsFeedback(text: String): Boolean{if(!isBluetoothConnected)returnfalseval statuscxrApi.sendTtsContent(text)if(statusValueUtil.CxrStatus.REQUEST_SUCCEED){// 关键必须通知 TTS 播放完成 cxrApi.notifyTtsAudioFinished()}returnstatusValueUtil.CxrStatus.REQUEST_SUCCEED}注意调用 sendTtsContent 后必须再调用 notifyTtsAudioFinished()否则 TTS 可能播放不完整。UI 界面简洁实用优先主界面采用经典的列表式布局● 顶部眼镜连接状态指示器 连接按钮● 中间搜索框 亲戚卡片列表● 右下角浮动添加按钮布局结构!-- layout/activity_main.xml --androidx.coordinatorlayout.widget.CoordinatorLayout!-- 顶部 Toolbar --com.google.android.material.appbar.AppBarLayoutandroidx.appcompat.widget.Toolbar app:title亲戚称呼助手//com.google.android.material.appbar.AppBarLayoutLinearLayout!-- 眼镜连接状态卡片 --androidx.cardview.widget.CardViewLinearLayoutView android:idid/connection_indicator/!-- 状态指示灯 --TextView android:idid/tv_connection_status/MaterialButton android:idid/btn_connect//LinearLayout/androidx.cardview.widget.CardView!-- 搜索框 --TextInputLayout app:startIconDrawabledrawable/ic_searchTextInputEditText android:idid/et_search//TextInputLayout!-- 亲戚列表 --RecyclerView android:idid/recycler_view//LinearLayout!-- 添加按钮 --FloatingActionButton android:idid/fab_add//androidx.coordinatorlayout.widget.CoordinatorLayout列表项卡片每张卡片展示一个亲戚的关键信息并提供同步到眼镜按钮!-- layout/item_relative.xml --androidx.cardview.widget.CardViewLinearLayout android:orientationvertical!-- 名字大字加粗 --TextView android:idid/tv_nameandroid:textSize18spandroid:textStylebold/!-- 称呼标签 关系描述 --LinearLayout android:orientationhorizontalTextView android:idid/tv_titleandroid:backgrounddrawable/bg_tag/TextView android:idid/tv_relation//LinearLayout!-- 拜年话术预览最多两行 --TextView android:idid/tv_greetingandroid:maxLines2android:ellipsizeend/!-- 同步按钮 --Button android:idid/btn_syncandroid:text同步到眼镜//LinearLayout/androidx.cardview.widget.CardViewActivity 核心逻辑// MainActivity.kt class MainActivity:AppCompatActivity(){private val glassesManagerRokidGlassesManager override fun onCreate(savedInstanceState: Bundle?){// 初始化数据 RelativeRepository.init(this)// 设置眼镜连接回调 glassesManager.setConnectionCallback(object:ConnectionCallback{override funonConnected(){updateConnectionStatus(true)Snackbar.make(binding.root,眼镜连接成功, Snackbar.LENGTH_SHORT).show()}override funonDisconnected(){updateConnectionStatus(false)}//...})// 搜索功能 binding.etSearch.addTextChangedListener{text -filterRelatives(text?.toString()?:)}}private fun syncToGlasses(relative: Relative){if(!glassesManager.isBluetoothConnected){Snackbar.make(binding.root,请先连接眼镜, Snackbar.LENGTH_SHORT).show()return}val textrelative.toGlassDisplayText()glassesManager.sendTextToGlasses(text, object:SendCallback{override funonSuccess(){Snackbar.make(binding.root,已同步到眼镜, Snackbar.LENGTH_SHORT).show()// 语音提示 glassesManager.sendTtsFeedback(${relative.title}的信息)}override fun onFailed(errorMsg: String){Snackbar.make(binding.root,同步失败:$errorMsg, Snackbar.LENGTH_LONG).show()}})}}踩坑实录那些文档没告诉你的事坑一蓝牙权限动态申请Android 12API 31对蓝牙权限做了重大调整将原来的 BLUETOOTH 和 BLUETOOTH_ADMIN 拆分为更细粒度的 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT。关键点这些新权限必须运行时动态申请。仅 Manifest 声明在 Release 版本中会直接崩溃。private fun checkAndRequestPermissions(): Boolean{val permissionsif(Build.VERSION.SDK_INTBuild.VERSION_CODES.S){arrayOf(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.ACCESS_FINE_LOCATION)}else{arrayOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_FINE_LOCATION)}val notGrantedpermissions.filter{ActivityCompat.checkSelfPermission(this, it)!PackageManager.PERMISSION_GRANTED}if(notGranted.isEmpty())returntrueActivityCompat.requestPermissions(this, notGranted.toTypedArray(), REQUEST_PERMISSIONS)returnfalse}坑二提词器场景必须先打开这个问题让我调试了整整一下午。sendStream 调用返回成功但眼镜端什么都没显示。最后发现必须先调用 controlScene 打开提词器场景才能发送数据。// 正确顺序 cxrApi.controlScene(WORD_TIPS, true, null)// 先打开场景 cxrApi.sendStream(WORD_TIPS, data,...)// 再发送数据坑三中文编码第一次发送中文内容眼镜上显示一堆乱码。原因是 toByteArray() 默认使用系统编码在某些设备上可能不是 UTF-8。// 错误 streamtext.toByteArray()// 正确 streamtext.toByteArray(Charsets.UTF_8)最终效果功能清单功能状态说明亲戚列表✅支持按名字/称呼/关系搜索添加/编辑/删除✅表单输入自动生成话术眼镜连接✅自动发现已配对设备眼镜同步✅一键发送称呼话术TTS 播报✅语音反馈同步成功预设数据✅内置 20 条常见亲戚眼镜端显示效果当你在手机上点击同步到眼镜后眼镜屏幕上会立即显示┌─────────────────────────────┐ │ │ │ 王芳 │ │ │ │ 称呼大姨 │ │ 关系妈妈的姐姐 │ │ │ │ ────── 拜年话术 ────── │ │ │ │ 大姨新年好 │ │ 祝您身体健康万事如意 │ │ │ └─────────────────────────────┘春节拜年时当亲戚走过来你只需要悄悄瞟一眼眼镜称呼和话术尽收眼底从容应对再也不会叫错人了。总结与展望这个项目从想法到完成只用了两天时间代码量不大约 800 行但确实解决了一个真实痛点。技术亮点● CXR-M SDK 的正确使用方式场景控制 数据发送的顺序问题● Android 12 蓝牙权限的正确处理● 简洁实用的数据模型设计未来改进方向● 加入语音识别说这个是谁自动识别并显示● 支持拍照识别亲戚需要人脸识别技术● 关系图谱可视化直观展示家族关系● 云端数据同步换手机不丢数据项目源码RelativeTitleHelper/相关资源● CXR-M SDK 官方文档● Rokid 开发者论坛

相关新闻

零基础学微信小程序前端(原生JS):从0到1写第一个可交互页面

零基础学微信小程序前端(原生JS):从0到1写第一个可交互页面

目录 一、小程序前端的核心差异 二、前期准备:微信开发者工具搭建 三、核心知识点:小程序前端的目录结构 四、实操:写第一个可交互页面 1. 编写页面结构(index.wxml) 2. 编写页面样式(index.wxss&…

2026/5/17 8:23:00 阅读更多 →
工业组态网关通过MQTT协议实现数据集成监控

工业组态网关通过MQTT协议实现数据集成监控

方案介绍某集团中心需要对A、B、C三地进行监控,彼此之间都没有网络通讯。其中需要将A地实验室的温度、湿度等实时数据,B地空调的风阀、温湿度等实时数据,C地泵站的水泵启停、运行时间等实时数据通过MQTT协议对接到集团中心的管理平台中&#…

2026/7/5 22:41:05 阅读更多 →
深拷贝和浅拷贝的区别?如何实现深拷贝?

深拷贝和浅拷贝的区别?如何实现深拷贝?

一、什么是浅拷贝(Shallow Copy) 浅拷贝:只复制对象的第一层,嵌套对象仍然共享引用。 示例 const obj {name: "Tom",address: {city: "Beijing"} }const copy { ...obj }copy.address.city "Shang…

2026/7/3 23:47:49 阅读更多 →

最新新闻

位置编码外推实战:从BERT 512到26万token的3种延拓策略

位置编码外推实战:从BERT 512到26万token的3种延拓策略

位置编码外推实战:从BERT 512到26万token的3种延拓策略当处理长文本序列时,BERT等Transformer模型面临一个根本性限制——位置编码的长度约束。传统BERT模型最多只能处理512个token,这严重制约了其在长文档理解、基因组分析等场景的应用潜力。…

2026/7/6 0:11:20 阅读更多 →
如何彻底告别重复点击:AutoClicker鼠标自动化完全指南

如何彻底告别重复点击:AutoClicker鼠标自动化完全指南

如何彻底告别重复点击:AutoClicker鼠标自动化完全指南 【免费下载链接】AutoClicker AutoClicker is a useful simple tool for automating mouse clicks. 项目地址: https://gitcode.com/gh_mirrors/au/AutoClicker 还在为每天重复的鼠标点击任务感到疲惫吗…

2026/7/6 0:11:20 阅读更多 →
DQN 算法实战:CartPole-v0 环境 1000 轮训练实现 200 分满分

DQN 算法实战:CartPole-v0 环境 1000 轮训练实现 200 分满分

DQN算法实战:从零构建CartPole智能体的完整指南1. 环境准备与基础概念在开始构建DQN智能体之前,我们需要先理解几个核心概念。CartPole-v0是OpenAI Gym中的一个经典控制问题,目标是让小车上的杆子保持直立不倒下。这个环境有四个状态变量&…

2026/7/6 0:11:20 阅读更多 →
OpenCV 4.8 双目立体匹配实战:BM/SGBM/GC 3种算法在Middlebury数据集上的精度与速度对比

OpenCV 4.8 双目立体匹配实战:BM/SGBM/GC 3种算法在Middlebury数据集上的精度与速度对比

OpenCV 4.8 双目立体匹配实战:BM/SGBM/GC算法在Middlebury数据集上的精度与速度对比双目立体视觉作为三维重建的核心技术之一,其核心挑战在于如何高效准确地计算左右图像间的视差图。OpenCV作为计算机视觉领域的瑞士军刀,提供了Block Matchin…

2026/7/6 0:07:19 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性:5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域,单元测试是保证代码质量的重要环节。当应用涉及数据库操作时,测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

月新闻