AR 健身教练:形随心动 - 基于 Rokid CXR-M SDK 的实践落地
基于 Rokid CXR-M SDK 开发 AR 健身应用形随心动的技术实践。涵盖 SDK 配置、权限管理、设备连接、AI 动作识别、AR 界面渲染及数据可视化等核心功能实现。通过分层架构设计解决居家健身指导痛点,并探讨了动作精度优化、性能调优及功耗管理等挑战与解决方案。

基于 Rokid CXR-M SDK 开发 AR 健身应用形随心动的技术实践。涵盖 SDK 配置、权限管理、设备连接、AI 动作识别、AR 界面渲染及数据可视化等核心功能实现。通过分层架构设计解决居家健身指导痛点,并探讨了动作精度优化、性能调优及功耗管理等挑战与解决方案。

在当今快节奏的都市生活中,健身已成为许多人保持健康的重要方式。然而,居家健身面临一个普遍痛点:缺乏专业指导,容易因动作不规范导致运动损伤,同时低头看手机或平板的体验也大大降低了健身的沉浸感和效率。
根据《2024 年中国健身行业白皮书》显示,超过 65% 的居家健身用户表示"缺乏专业指导"是他们放弃健身的主要原因。而 Rokid Glasses 作为一款轻量级 AR 眼镜,其独特的"抬头即见"交互方式,为解决这一问题提供了绝佳的硬件基础。
"形随心动"创意的诞生源于一个简单但关键的观察:如果能将专业教练"投射"到用户视野中,实时指导动作,同时提供直观的数据反馈,那么居家健身体验将发生质的飞跃。通过 Rokid CXR-M SDK 的 AI 场景、自定义页面和提词器功能,我们能够实现这一愿景。
CXR-M SDK 是面向移动端的开发工具包,主要用于构建手机端与 Rokid Glasses 的控制和协同应用。开发者可以通过 CXR-M SDK 与眼镜建立稳定连接,实现数据通信、实时音视频获取以及场景自定义。它适合需要在手机端进行界面交互、远程控制或与眼镜端配合完成复杂功能的应用。目前 CXR-M SDK 仅提供 Android 版本。
CXR-M SDK 采用 Maven 在线管理 SDK Package。
Maven 仓库地址:https://maven.rokid.com/repository/maven-public/
找到 settings.gradle.kts,并在 dependencyResolutionManagement 节点的 repositories 中添加 Maven 仓库。
CXR-M SDK Package:com.rokid.cxr:client-m:1.0.1-20250812.080117-2。
在 build.gradle.kts 的 dependencies 节点中添加依赖。
注意:SDK 需要设置 minSdk≥28。
如果和项目中已有版本冲突,请优先选用 SDK 中对应的版本。
CXR-M SDK 需要申请网络、Wi-Fi、Bluetooth(蓝牙权限需要同步申请 FINE_LOCATION 权限)等权限,在 AndroidManifest.xml 中申请以下是最小权限集:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
在 CXR-M SDK 使用前,请先进行必要权限动态申请。注意在权限不足的情况下,SDK 将不可用。
示例:
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "MainActivity"
// Request Code
const val REQUEST_CODE_PERMISSIONS = 100
// Required Permissions
private val REQUIRED_PERMISSIONS = mutableListOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
add(Manifest.permission.BLUETOOTH_SCAN)
add(Manifest.permission.BLUETOOTH_CONNECT)
}
}.toTypedArray()
}
// Permission
private val permissionGrantedResult = MutableLiveData<Boolean?>()
override fun onCreate(savedInstanceState: Bundle?) {
// Other Code
// Request Permissions
permissionGrantedResult.postValue(null)
requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
// Observe Permission Result
permissionGrantedResult.observe(this) { result ->
if (it == true) {
// Permission All Granted
} else {
// Some Permission Denied or Not Started
}
}
}
override fun onRequestPermissionsResult {
.onRequestPermissionsResult(requestCode, permissions, grantResults)
(requestCode == REQUEST_CODE_PERMISSIONS.hashCode()) {
allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
permissionGrantedResult.postValue(allGranted)
}
}
}
"形随心动"采用分层架构设计,确保系统稳定性和可扩展性:
应用层(Android App) → CXR-M SDK → Rokid Glasses(YodaOS) → 硬件传感器
架构说明:
| 组件 | 功能 | SDK 实现方式 |
|---|---|---|
| 实时动作识别模块 | 通过摄像头捕捉用户动作,分析动作质量 | AI 场景、自定义页面 |
| 个性化计划模块 | 根据用户数据定制健身计划 | 自定义页面、数据同步 |
| AR 教练界面 | 3D 显示虚拟教练,实时指导动作 | 自定义页面、提词器 |
| 数据可视化模块 | 实时显示心率、消耗卡路里等数据 | 自定义页面、媒体操作 |
| 社交互动模块 | 支持好友挑战、进度分享 | 数据通信、自定义页面 |
实现关键点:
| 组件 | 关键技术点 | SDK 实现方式 | 实现难度 |
|---|---|---|---|
| 设备连接 | 蓝牙稳定连接 | CxrApi.getInstance().initBluetooth() | ★★☆ |
| AI 场景 | 动作识别与分析 | setAiEventListener() + controlScene() | ★★★★ |
| AR 界面 | 3D 指导界面渲染 | openCustomView() + JSON 布局 | ★★★ |
| 数据同步 | 实时数据传输 | sendStream() + setMediaFilesUpdateListener() | ★★ |
| 社交功能 | 好友挑战实现 | controlScene() + 数据通信 | ★★★ |
首先,需要确保手机与 Rokid Glasses 稳定连接,这是所有功能的基础。
// 设备连接初始化
class FitnessApp : AppCompatActivity() {
private val REQUIRED_PERMISSIONS = mutableListOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
add(Manifest.permission.BLUETOOTH_SCAN)
add(Manifest.permission.BLUETOOTH_CONNECT)
}
}.toTypedArray()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 检查并请求必要权限
if (!hasPermissions(REQUIRED_PERMISSIONS)) {
requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
} else {
initializeDeviceConnection()
}
}
private fun hasPermissions(permissions: Array<String>): Boolean {
return permissions.all { ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED }
}
private fun initializeDeviceConnection() {
// 查找 Rokid 设备
val scanner = BluetoothLeScannerCompat.getScanner()
scanner.startScan(
listOf(ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString("00009100-0000-1000-8000-00805f9b34fb"))
.build()),
ScanSettings.Builder().build(),
scanCallback
)
}
private val scanCallback = object : ScanCallback() {
{
.onScanResult(callbackType, result)
device = result.device
CxrApi.getInstance().initBluetooth(, device, : BluetoothStatusCallback {
{
Log.d(, )
CxrApi.getInstance().connectBluetooth(, socketUuid, macAddress, )
}
{
Log.d(, )
initializeAiScene()
}
{
Log.e(, )
}
{
Log.e(, )
}
})
}
}
{
CxrApi.getInstance().setAiEventListener( : AiEventListener {
{
(eventType) {
ValueUtil.CxrAiEventType.ACTION_RECOGNITION -> {
handleActionRecognition()
}
}
}
})
CxrApi.getInstance().controlScene(ValueUtil.CxrSceneType.AI_SCENE, , )
}
}
这是"形随心动"的核心功能,利用 Rokid Glasses 的摄像头和 AI 能力,实时分析用户动作。
// 动作识别与纠正实现
private fun startExercise(exerciseType: String) {
// 设置动作识别参数
val actionParams = HashMap<String, String>()
actionParams["exerciseType"] = exerciseType
actionParams["threshold"] = "0.85" // 识别阈值
// 启动 AI 场景进行动作识别
CxrApi.getInstance().controlScene(
ValueUtil.CxrSceneType.AI_SCENE, true, actionParams
)
// 设置 AI 事件监听
CxrApi.getInstance().setAiEventListener(object : AiEventListener {
override fun onAiEvent(eventType: ValueUtil.CxrAiEventType?, data: String?) {
when (eventType) {
ValueUtil.CxrAiEventType.ACTION_RECOGNITION -> {
val recognitionResult = Gson().fromJson(data, ActionRecognitionResult::class.java)
if (recognitionResult.accuracy > 0.8) {
// 动作正确,显示正面反馈
showPositiveFeedback(recognitionResult.exerciseName, "动作标准!")
} else {
// 动作不标准,显示纠正提示
showCorrectionFeedback(recognitionResult.exerciseName, recognitionResult.correctionMessage)
}
}
}
}
})
}
private fun showCorrectionFeedback(exerciseName: String, message: ) {
CxrApi.getInstance().controlScene(
ValueUtil.CxrSceneType.WORD_TIPS, ,
)
text =
CxrApi.getInstance().sendStream(
ValueUtil.CxrStreamType.WORD_TIPS, text.toByteArray(), ,
: SendStatusCallback {
{
Log.d(, )
}
{
Log.e(, )
}
}
)
}
{
arCoachJson = .trimIndent()
CxrApi.getInstance().openCustomView(arCoachJson)
CxrApi.getInstance().setCustomViewListener( : CustomViewListener {
{
Log.d(, )
}
{
Log.d(, )
}
})
}
根据用户输入的身体数据和健身目标,定制专属健身计划。
// 个性化健身计划实现
class PersonalizedPlanActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_personalized_plan)
// 获取用户输入数据
val age = findViewById<EditText>(R.id.et_age).text.toString().toInt()
val weight = findViewById<EditText>(R.id.et_weight).text.toString().toFloat()
val height = findViewById<EditText>(R.id.et_height).text.toString().toFloat()
val goal = findViewById<Spinner>(R.id.spinner_goal).selectedItem.toString()
// 根据数据生成计划
val plan = generatePersonalizedPlan(age, weight, height, goal)
// 显示计划
displayPlan(plan)
// 保存计划到 Rokid Glasses
savePlanToGlasses(plan)
}
private fun generatePersonalizedPlan(age: Int, weight: Float, height: Float, goal: String): Plan {
// 实际应用中,这里会根据算法生成计划
// 为简化示例,直接返回预设计划
return Plan(
"30 天减脂计划",
listOf(
Exercise("深蹲", "3 组×15 次", "保持膝盖不超过脚尖", "squats"),
Exercise("平板支撑", "3 组×30 秒", "保持身体成直线", "plank"),
Exercise("跳绳", , , )
)
)
}
{
planAdapter = PlanAdapter(plan.exercises)
findViewById<RecyclerView>(R.id.rv_plan).adapter = planAdapter
}
{
planJson = Gson().toJson(plan)
CxrApi.getInstance().sendStream(
ValueUtil.CxrStreamType.PERSONALIZED_PLAN, planJson.toByteArray(), ,
: SendStatusCallback {
{
Log.d(, )
}
{
Log.e(, )
}
}
)
}
}
将健身数据以 AR 形式展示,让用户无需低头即可获取关键信息。
// 数据可视化与实时反馈
class DataVisualizationActivity : AppCompatActivity() {
private var heartRate = 0
private var caloriesBurned = 0f
private var exerciseTime = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_data_visualization)
// 初始化 AR 界面
setupARDataView()
// 开始计时
startExerciseTimer()
// 模拟数据更新
startDataUpdates()
}
private fun setupARDataView() {
// 创建 AR 数据视图的 JSON 布局
val dataViewJson = """
{
"type": "LinearLayout",
"props": {
"layout_width": "match_parent",
"layout_height": "match_parent",
"orientation": "vertical",
"gravity": "center",
"backgroundColor": "#00000000"
},
"children": [
{
"type": "TextView",
"props": {
"id": "tv_heart_rate",
"layout_width": "wrap_content",
"layout_height": "wrap_content",
"text": "心率:72",
"textSize": "20sp",
"textColor": "#FF00FF00",
"textStyle": "bold"
}
},
{
"type": "TextView",
"props": {
"id": "tv_calories",
"layout_width": "wrap_content",
"layout_height": "wrap_content",
"text": "卡路里:0",
"textSize": "20sp",
"textColor": "#FF00FF00",
"textStyle": "bold",
"marginTop": "20dp"
}
},
{
"type": "TextView",
"props": {
"id": "tv_time",
"layout_width": "wrap_content",
"layout_height": "wrap_content",
"text": "时间:00:00",
"textSize": "20sp",
"textColor": "#FF00FF00",
"textStyle": "bold",
"marginTop": "20dp"
}
}
]
}
""".trimIndent()
CxrApi.getInstance().openCustomView(dataViewJson)
}
{
updateData = listOf(
UpdateAction(, , , ),
UpdateAction(, , , ),
UpdateAction(, , , )
)
CxrApi.getInstance().updateCustomView(Gson().toJson(updateData))
}
: String {
minutes = seconds /
remainingSeconds = seconds %
String.format(, minutes, remainingSeconds)
}
{
handler = Handler(Looper.getMainLooper())
handler.postDelayed( : Runnable {
{
heartRate = (heartRate + ).coerceAtMost()
caloriesBurned +=
exerciseTime +=
updateDataView()
handler.postDelayed(, )
}
}, )
}
{
startTime = System.currentTimeMillis()
timerHandler = Handler(Looper.getMainLooper())
timerHandler.postDelayed( : Runnable {
{
exerciseTime = ((System.currentTimeMillis() - startTime) / ).toInt()
updateDataView()
timerHandler.postDelayed(, )
}
}, )
}
}
通过 Rokid Glasses 的社交功能,实现与好友的健身互动。
// 社交健身挑战实现
class ChallengeActivity : AppCompatActivity() {
private val challenges = mutableListOf<Challenge>(
Challenge("7 天深蹲挑战", "完成 7 天深蹲训练,每天 100 次", 7, 100),
Challenge("30 天心率挑战", "保持心率在 120-150 之间", 30, 0)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_challenge)
// 显示挑战列表
setupChallengeList()
// 添加好友挑战
findViewById<Button>(R.id.btn_add_friend_challenge).setOnClickListener {
showAddFriendChallengeDialog()
}
}
private fun setupChallengeList() {
val adapter = ChallengeAdapter(challenges)
findViewById<RecyclerView>(R.id.rv_challenges).adapter = adapter
}
private fun showAddFriendChallengeDialog() {
val builder = AlertDialog.Builder(this)
builder.setTitle("添加好友挑战")
val input = EditText(this)
input.hint = "输入好友 ID"
builder.setView(input)
builder.setPositiveButton("确定") { _, _ ->
val friendId = input.text.toString()
createFriendChallenge(friendId)
}
builder.setNegativeButton(, )
builder.show()
}
{
challenge = Challenge(
,
,
,
)
saveChallengeToGlasses(challenge)
sendChallengeToFriend(friendId, challenge)
}
{
challengeJson = Gson().toJson(challenge)
CxrApi.getInstance().sendStream(
ValueUtil.CxrStreamType.CHALLENGE, challengeJson.toByteArray(), ,
: SendStatusCallback {
{
Log.d(, )
}
{
Log.e(, )
}
}
)
}
{
Log.d(, )
Toast.makeText(, , Toast.LENGTH_SHORT).show()
}
}
挑战:在实际环境中,由于光线变化和用户动作差异,AI 动作识别准确率有时不足。
解决方案:
// 动态阈值调整示例
private fun adjustRecognitionThreshold(userProfile: UserProfile): Float {
return when {
userProfile.age < 25 -> 0.85f
userProfile.age < 40 -> 0.80f
else -> 0.75f
}
}
挑战:AR 界面在 Rokid Glasses 上运行时,帧率不稳定。
解决方案:
setCustomViewListener 进行界面渲染优化// 优化 AR 界面渲染
CxrApi.getInstance().setCustomViewListener(object : CustomViewListener {
override fun onCustomViewOpened() {
// 优化渲染
CxrApi.getInstance().setCustomViewRenderMode(ValueUtil.CxrRenderMode.FAST)
}
override fun onCustomViewClosed() {
// 恢复默认渲染模式
CxrApi.getInstance().setCustomViewRenderMode(ValueUtil.CxrRenderMode.DEFAULT)
}
})
挑战:AR 应用长时间运行导致 Rokid Glasses 电池消耗过快。
解决方案:
// 智能功耗管理
CxrApi.getInstance().setPowerOffTimeout(30) // 30 分钟后自动关机
CxrApi.getInstance().setScreenOffTimeout(60) // 60 秒后屏幕关闭
// 自动休眠功能
val exerciseTimer = object : CountDownTimer(300000, 1000) { // 5 分钟
override fun onTick(millisUntilFinished: Long) {
// 更新休眠计时
}
override fun onFinish() {
// 进入低功耗模式
CxrApi.getInstance().setGlassBrightness(3) // 降低亮度
CxrApi.getInstance().setGlassVolume(2) // 降低音量
Toast.makeText(this@FitnessApp, "进入低功耗模式", Toast.LENGTH_SHORT).show()
}
}.start()
"形随心动"已经证明了 Rokid CXR-M SDK 在 AR 健身场景中的强大能力,未来我们将进一步拓展:
"形随心动"不仅是一款健身应用,更是 Rokid CXR-M SDK 能力的全面展示。通过深度整合 SDK 的 AI 场景、自定义页面和提词器功能,我们实现了真正"抬头即见指导"的 AR 健身体验,解决了居家健身的核心痛点。
"形随心动"展示了 Rokid 生态的广阔应用前景,也为其他开发者提供了可借鉴的实践案例。我们相信,随着 Rokid SDK 的持续完善和生态的不断扩展,AR 技术将在更多生活场景中发挥重要作用,为用户带来前所未有的体验。
通过"形随心动"的实践,我们验证了 Rokid Glasses 与 CXR-M SDK 在 AI+AR 场景中的巨大潜力,也为 Rokid 开发者社区贡献了一个成功的应用案例。未来,我将继续深耕 AR 健身领域,为用户提供更专业、更智能的健身体验,同时为 Rokid 生态的繁荣贡献力量。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online