从社死边缘拯救我:用 AR 眼镜打造“亲戚称呼助手“

从社死边缘拯救我:用 AR 眼镜打造“亲戚称呼助手“

从社死边缘拯救我:用 AR 眼镜打造"亲戚称呼助手

一个真实的新年灾难

大年初二,我跟着新婚妻子回娘家。
刚进门,七大姑八大姨就围了上来。一位头发花白的阿姨笑盈盈地递过来一个红包,我脑子里嗡的一声——这到底是妻子的哪位亲戚?大姨?小姨?还是什么远房表姑?
“小张啊,还认识我不?”
我支支吾吾半天,最后还是妻子打了圆场:“这是大姨,小时候还抱过你呢!”
那一刻,我看到了大姨眼里的失望。这种社死现场,相信很多人都经历过:春节期间,走亲访友是必修课,但那些一年见一次的亲戚,名字和称呼根本记不住。尤其是刚结婚的新人、不常回家的打工人,简直是"称呼灾难"高发人群。
回家后,我下定决心:明年春节,我绝不能再叫错人。

在这里插入图片描述

思路:为什么是 AR 眼镜?

解决方案无非几种:
● 记在手机备忘录:掏手机、解锁、搜索,太慢,而且当着亲戚面查手机很不礼貌
● 记在小本本上:更尴尬,像是在做作弊小抄
● 让家人提醒:每次都要麻烦别人,不靠谱
想了很久,我注意到桌上的 Rokid AR 眼镜。眼镜有几个天然优势:

在这里插入图片描述
对比维度手机AR 眼镜
使用隐蔽性众人可见你在查手机只有自己能看到屏幕内容
操作便捷度掏出→解锁→搜索→查看抬眼即见,无需动手
社交压力明显在看手机,不礼貌自然地瞟一眼,谁也发现不了
响应速度打开APP需要几秒信息即时显示

项目搭建:从零开始集成 SDK

1. 创建项目并配置 Maven 仓库

首先是一个标准的 Android 项目,Kotlin 语言,minSdk 设为 28(SDK 硬性要求)。在 settings.gradle.kts 中添加 Rokid 的 Maven 仓库:

// settings.gradle.kts dependencyResolutionManagement { repositories { maven { url = uri("https://maven.rokid.com/repository/maven-public/")} google() mavenCentral()}}
在这里插入图片描述

2. 添加依赖项

在 app/build.gradle.kts 中引入 CXR-M SDK 和必要的 Android 组件:

// app/build.gradle.kts android { namespace ="com.rokid.relativehelper" compileSdk =34 defaultConfig { minSdk =28 targetSdk =34}} 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:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><!-- Android 12+ 蓝牙扫描/连接权限 --><uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /><!-- 定位权限(部分设备蓝牙扫描需要) --><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

关键提示:Android 12 以上,BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT 必须在运行时动态申请,仅 Manifest 声明会导致崩溃。

数据模型:如何存储亲戚信息?

先定义一个简洁的数据结构。每个亲戚条目需要:名字、称呼、关系描述、拜年话术,以及可选的拼音和备注:

// model/Relative.kt data class Relative( var id: Long =0, val name: String, // 亲戚名字,如"王芳" val title: String, // 称呼,如"大姨"、"叔叔" val relation: String, // 关系描述,如"妈妈的姐姐" val greetingTemplate: String, // 拜年话术模板 val phonetic: String? = null, // 名字拼音(可选,防止读错) val notes: String? = null // 备注(可选) ){ /** * 生成眼镜端显示的格式化文本 */ fun toGlassDisplayText(): String = buildString { appendLine("👤 $name") appendLine() appendLine("称呼:$title") appendLine("关系:$relation") appendLine() appendLine("────── 拜年话术 ──────") appendLine(greetingTemplate)}}
在这里插入图片描述


这个 toGlassDisplayText() 方法很关键——它决定了信息在眼镜上的呈现方式。我选择了简洁清晰的格式:

👤 王芳 称呼:大姨 关系:妈妈的姐姐 ────── 拜年话术 ────── 大姨新年好!祝您身体健康,万事如意! 

这样在眼镜上一眼就能看到关键信息。

数据持久化

亲戚数据需要持久化存储。考虑到数据量不大(通常几十条),我选择了最简单的 SharedPreferences + JSON 序列化方案:

// data/RelativeRepository.kt object RelativeRepository { private const val PREFS_NAME ="relative_helper_prefs" private const val KEY_RELATIVES ="relatives" private val relatives = mutableListOf<Relative>() fun init(context: Context){ loadFromPrefs(context)if(relatives.isEmpty()){ loadPresetData() // 首次运行,加载预设数据 }} fun searchRelatives(keyword: String): List<Relative>{if(keyword.isBlank())return relatives.toList() val lowerKeyword = keyword.lowercase()return relatives.filter { it.name.contains(keyword, ignoreCase =true)|| it.title.contains(keyword, ignoreCase =true)|| it.relation.contains(keyword, ignoreCase =true)}} private fun loadPresetData(){ // 内置 20 条常见亲戚数据,涵盖祖辈、父辈、同辈 relatives.addAll(listOf( Relative(1, "王芳", "大姨", "妈妈的姐姐", "大姨新年好!祝您身体健康,万事如意!"), Relative(2, "李明", "叔叔", "爸爸的弟弟", "叔叔过年好!祝您事业顺利,财源广进!"), // ... 更多预设 ))}}

此外,我还实现了一个贴心的小功能:根据称呼自动生成拜年话术。比如输入"爷爷",自动填充"爷爷新年好!祝您身体健康,长命百岁!"

fun generateDefaultGreeting(title: String): String = when (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( context = context, device = device, callback = object :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 fun onConnected(){ connectionCallback?.onConnected()} override fun onDisconnected(){ connectionCallback?.onDisconnected()} override fun onFailed(errorCode: ValueUtil.CxrBluetoothErrorCode?){ connectionCallback?.onFailed(getBluetoothErrorMessage(errorCode))}})}

踩坑提醒:SDK 的蓝牙连接是异步的,所有结果都通过回调返回。不要试图同步等待连接结果,会导致死锁。

发送数据到眼镜

数据发送是整个应用的关键功能。这里有一个必须注意的顺序:

  1. 先打开提词器场景
  2. 再发送文本数据
fun sendTextToGlasses(text: String, callback: SendCallback? = null): Boolean {if(!isBluetoothConnected){ callback?.onFailed("眼镜未连接")returnfalse} // 关键:必须先打开提词器场景! openWordTipsScene() val status = cxrApi.sendStream(type= ValueUtil.CxrStreamType.WORD_TIPS, stream = text.toByteArray(Charsets.UTF_8), // 注意编码 fileName ="relative_info.txt", cb = object :SendStatusCallback(){ override fun onSendSucceed(){ callback?.onSuccess()} override fun onSendFailed(errorCode: ValueUtil.CxrSendErrorCode?){ callback?.onFailed(getSendErrorMessage(errorCode))}})return status == ValueUtil.CxrStatus.REQUEST_SUCCEED } private fun openWordTipsScene(): Boolean { val status = cxrApi.controlScene( sceneType = ValueUtil.CxrSceneType.WORD_TIPS, openOrClose = true, otherParams = null )return status == ValueUtil.CxrStatus.REQUEST_SUCCEED }

我第一次调试时,sendStream 返回成功,但眼镜端什么都没显示。排查了半天才发现是场景没打开——这个坑踩得很痛。

TTS 语音播报

除了文字显示,SDK 还支持 TTS(文字转语音)功能,可以在同步信息后播放语音提示:

fun sendTtsFeedback(text: String): Boolean {if(!isBluetoothConnected)returnfalse val status = cxrApi.sendTtsContent(text)if(status == ValueUtil.CxrStatus.REQUEST_SUCCEED){ // 关键:必须通知 TTS 播放完成 cxrApi.notifyTtsAudioFinished()}return status == ValueUtil.CxrStatus.REQUEST_SUCCEED }

注意:调用 sendTtsContent 后,必须再调用 notifyTtsAudioFinished(),否则 TTS 可能播放不完整。

UI 界面:简洁实用优先

主界面采用经典的列表式布局:
● 顶部:眼镜连接状态指示器 + 连接按钮
● 中间:搜索框 + 亲戚卡片列表
● 右下角:浮动添加按钮

布局结构

<!-- layout/activity_main.xml --><androidx.coordinatorlayout.widget.CoordinatorLayout><!-- 顶部 Toolbar --><com.google.android.material.appbar.AppBarLayout><androidx.appcompat.widget.Toolbar app:title="亲戚称呼助手" /></com.google.android.material.appbar.AppBarLayout><LinearLayout><!-- 眼镜连接状态卡片 --><androidx.cardview.widget.CardView><LinearLayout><View android:id="@+id/connection_indicator" /><!-- 状态指示灯 --><TextView android:id="@+id/tv_connection_status" /><MaterialButton android:id="@+id/btn_connect" /></LinearLayout></androidx.cardview.widget.CardView><!-- 搜索框 --><TextInputLayout app:startIconDrawable="@drawable/ic_search"><TextInputEditText android:id="@+id/et_search" /></TextInputLayout><!-- 亲戚列表 --><RecyclerView android:id="@+id/recycler_view" /></LinearLayout><!-- 添加按钮 --><FloatingActionButton android:id="@+id/fab_add" /></androidx.coordinatorlayout.widget.CoordinatorLayout>

列表项卡片

每张卡片展示一个亲戚的关键信息,并提供"同步到眼镜"按钮:

<!-- layout/item_relative.xml --><androidx.cardview.widget.CardView><LinearLayout android:orientation="vertical"><!-- 名字(大字加粗) --><TextView android:id="@+id/tv_name" android:textSize="18sp" android:textStyle="bold" /><!-- 称呼标签 + 关系描述 --><LinearLayout android:orientation="horizontal"><TextView android:id="@+id/tv_title" android:background="@drawable/bg_tag" /><TextView android:id="@+id/tv_relation" /></LinearLayout><!-- 拜年话术预览(最多两行) --><TextView android:id="@+id/tv_greeting" android:maxLines="2" android:ellipsize="end" /><!-- 同步按钮 --><Button android:id="@+id/btn_sync" android:text="同步到眼镜" /></LinearLayout></androidx.cardview.widget.CardView>

Activity 核心逻辑

// MainActivity.kt class MainActivity :AppCompatActivity(){ private val glassesManager = RokidGlassesManager override fun onCreate(savedInstanceState: Bundle?){ // 初始化数据 RelativeRepository.init(this) // 设置眼镜连接回调 glassesManager.setConnectionCallback(object : ConnectionCallback { override fun onConnected(){ updateConnectionStatus(true) Snackbar.make(binding.root, "眼镜连接成功", Snackbar.LENGTH_SHORT).show()} override fun onDisconnected(){ 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 text = relative.toGlassDisplayText() glassesManager.sendTextToGlasses(text, object : SendCallback { override fun onSuccess(){ 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 12(API 31)对蓝牙权限做了重大调整,将原来的 BLUETOOTH 和 BLUETOOTH_ADMIN 拆分为更细粒度的 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT。
关键点:这些新权限必须运行时动态申请。仅 Manifest 声明在 Release 版本中会直接崩溃。

private fun checkAndRequestPermissions(): Boolean { val permissions =if(Build.VERSION.SDK_INT >= Build.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 notGranted = permissions.filter { ActivityCompat.checkSelfPermission(this, it)!= PackageManager.PERMISSION_GRANTED }if(notGranted.isEmpty())returntrue ActivityCompat.requestPermissions(this, notGranted.toTypedArray(), REQUEST_PERMISSIONS)returnfalse}

坑二:提词器场景必须先打开

这个问题让我调试了整整一下午。sendStream 调用返回成功,但眼镜端什么都没显示。
最后发现:必须先调用 controlScene 打开提词器场景,才能发送数据。

// 正确顺序 cxrApi.controlScene(WORD_TIPS, true, null) // 先打开场景 cxrApi.sendStream(WORD_TIPS, data, ...) // 再发送数据 

坑三:中文编码

第一次发送中文内容,眼镜上显示一堆乱码。原因是 toByteArray() 默认使用系统编码,在某些设备上可能不是 UTF-8。

// 错误 stream = text.toByteArray() // 正确 stream = text.toByteArray(Charsets.UTF_8)

最终效果

功能清单

功能状态说明
亲戚列表支持按名字/称呼/关系搜索
添加/编辑/删除表单输入,自动生成话术
眼镜连接自动发现已配对设备
眼镜同步一键发送称呼+话术
TTS 播报语音反馈同步成功
预设数据内置 20 条常见亲戚

眼镜端显示效果

当你在手机上点击"同步到眼镜"后,眼镜屏幕上会立即显示:

┌─────────────────────────────┐ │ │ │ 👤 王芳 │ │ │ │ 称呼:大姨 │ │ 关系:妈妈的姐姐 │ │ │ │ ────── 拜年话术 ────── │ │ │ │ 大姨新年好! │ │ 祝您身体健康,万事如意! │ │ │ └─────────────────────────────┘ 

春节拜年时,当亲戚走过来,你只需要悄悄瞟一眼眼镜,称呼和话术尽收眼底,从容应对,再也不会叫错人了。

总结与展望

这个项目从想法到完成只用了两天时间,代码量不大(约 800 行),但确实解决了一个真实痛点。
技术亮点:
● CXR-M SDK 的正确使用方式(场景控制 + 数据发送的顺序问题)
● Android 12+ 蓝牙权限的正确处理
● 简洁实用的数据模型设计

未来改进方向:
● 加入语音识别,说"这个是谁"自动识别并显示
● 支持拍照识别亲戚(需要人脸识别技术)
● 关系图谱可视化,直观展示家族关系
● 云端数据同步,换手机不丢数据

项目源码:RelativeTitleHelper/

相关资源:
CXR-M SDK 官方文档
Rokid 开发者论坛

Read more

AI 智能编码工具:重塑开发效率的革命,从 GitHub Copilot 到国产新秀的全面解析

AI 智能编码工具:重塑开发效率的革命,从 GitHub Copilot 到国产新秀的全面解析

目录 引言 一、主流智能编码工具深度测评:从功能到实战 1. GitHub Copilot:AI 编码的 “开山鼻祖” 核心特性与实战代码 优缺点总结 2. Baidu Comate:文心大模型加持的 “国产之光” 核心特性与实战代码 优缺点总结 3. 通义灵码:阿里云的 “企业级编码助手” 核心特性与实战代码 优缺点总结 引言 作为一名拥有 8 年开发经验的程序员,我曾无数次在深夜对着屏幕反复调试重复代码,也因记不清框架语法而频繁切换浏览器查询文档。直到 2021 年 GitHub Copilot 问世,我才第一次感受到:AI 不仅能辅助编码,更能彻底改变开发模式。如今,智能编码工具已从 “尝鲜选项” 变为 “必备工具”,它们像经验丰富的结对编程伙伴,能精准补全代码、生成测试用例、

[论文阅读] (38)基于大模型的威胁情报分析与知识图谱构建论文总结(读书笔记)

[论文阅读] (38)基于大模型的威胁情报分析与知识图谱构建论文总结(读书笔记)

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座,并分享给大家,希望您喜欢。由于作者的英文水平和学术能力不高,需要不断提升,所以还请大家批评指正,非常欢迎大家给我留言评论,学术路上期待与您前行,加油。 忙碌的五月终于过去,忙到来不及分享技术,六月开启,继续更新博客,感谢大家的支持,久等了! 本文旨在系统梳理大语言模型(LLM)在网络安全与威胁情报分析中的最新研究进展,侧重知识图谱构建、攻击行为建模以及模型泛化与推理能力等关键技术维度。结合作者当前的研究方向与兴趣,本文挑选并归纳了多篇代表性论文,其中重点详述的为与团队工作紧密相关、具有实际借鉴价值的工作。这些大佬的文章真心值得我们学习,希望本文对您有所帮助,写得不足之处还请海涵。 在逐篇阅读过程中,笔者特别关注以下要素:论文所提出的系统框架图、大模型的技术创新点、与知识图谱的融合机制、以及所采用的实验验证方法和开源代码。这些内容不仅拓宽了对 LLM 能力边界的理解,也为我们后续在威胁情报建模与网络安全防御方面提供了一定的路径指引。 同时,欢迎各位老师和大佬补充相关高质量论文,后续笔者也将不断更新与精炼此系列内容。希望

智能创作与优化新时代:【ChatGPT-4o】在【数学建模】、【AI绘画】、【海报设计】与【论文优化】中的创新应用

智能创作与优化新时代:【ChatGPT-4o】在【数学建模】、【AI绘画】、【海报设计】与【论文优化】中的创新应用

目录 1. 引言 什么是ChatGPT4o? 背景与发展历史 2.chatgpt4o数学建模 常见的数学建模专业术语及其简要说明 一个具体的代码例子 问题描述 代码实现  代码说明 运行结果 3.chatgpt4o在论文 1.例如生成基于标签的推荐系统模型及算法研究  1. 摘要 2. 引言 3. 文献综述 4. 模型与算法 5. 实验与分析 6. 结论与展望 7. 参考文献 案例背景 2.具体应用场景 1. 摘要优化 原稿: ChatGPT优化后的版本: 优化点: 2. 引言部分的结构优化 原稿: ChatGPT优化后的版本: 优化点: 3. 方法部分的细化与完善 原稿: ChatGPT优化后的版本: 4. 结论的增强

万字长文带你梳理Llama开源家族:从Llama-1到Llama-3,看这一篇就够了!

万字长文带你梳理Llama开源家族:从Llama-1到Llama-3,看这一篇就够了!

在AI领域,大模型的发展正以前所未有的速度推进技术的边界。 北京时间4月19日凌晨,Meta在官网上官宣了Llama-3,作为继Llama-1、Llama-2和Code-Llama之后的第三代模型,Llama-3在多个基准测试中实现了全面领先,性能优于业界同类最先进的模型。 纵观Llama系列模型,从版本1到3,展示了大规模预训练语言模型的演进及其在实际应用中的显著潜力。这些模型不仅在技术上不断刷新纪录,更在商业和学术界产生了深远的影响。因此,对Llama模型不同版本之间的系统对比,不仅可以揭示技术进步的具体细节,也能帮助我们理解这些高级模型如何解决现实世界的复杂问题。 1、Llama进化史 本节将对每个版本的Llama模型进行简要介绍,包括它们发布的时间和主要特点。 1.1 Llama-1 系列 Llama-1 [1]是Meta在2023年2月发布的大语言模型,是当时性能非常出色的开源模型之一,有7B、13B、30B和65B四个参数量版本。Llama-1各个参数量版本都在超过1T token的语料上进行了预训训练,其中,最大的65B参数的模型在2,048张A100 80