前言
户外徒步中,传统导航存在低头看手机致险、岔路记录难关联位置、紧急情况环境信息同步难等问题。CXR-M SDK 可实现手机与 Rokid Glasses 稳定连接,调用录音、拍照等能力,接入 YodaOS-Sprite 交互流程,但户外开发需解决蓝牙断连、强光显示不清、离线同步失败等问题。本文围绕 "户外徒步协同导航系统",涵盖 SDK 环境搭建、功能落地及避坑指南,助力开发者快速转化 SDK 能力为实用功能。
CXR-M SDK 核心能力与适用范围
CXR-M SDK 是移动端开发工具包(仅 Android 版本),用于构建手机与 Rokid Glasses 的控制协同应用,支持数据通信、实时音视频获取及场景自定义,可接入 YodaOS-Sprite 交互流程,调用文件互传、录音、拍照等 Rokid Assist Service 服务。
官方文档参考: custom.rokid.com/prod/rokid_...

CXR-M SDK 导入配置指南
以 Kotlin DSL(build.gradle.kts)为例,从三方面说明配置流程:
1.Maven 仓库配置:资源拉取基础
在<font style="color:rgba(0, 0, 0, 0.85);">settings.gradle.kts</font>
的<font style="color:rgba(0, 0, 0, 0.85);">dependencyResolutionManagement</font>
节点中添加 Rokid Maven 仓库,保留基础仓库:
kotlin
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
// 新增 Rokid Maven 仓库,用于拉取 CXR-M SDK
maven { url = uri("https://maven.rokid.com/repository/maven-public/") }
google()
mavenCentral()
}
}

2.SDK 与依赖导入:版本兼容与环境要求
模块级<font style="color:rgba(0, 0, 0, 0.85);">build.gradle.kts</font>
中添加核心依赖,设置<font style="color:rgba(0, 0, 0, 0.85);">minSdk=28</font>
(最低支持 Android 9.0),并导入配套依赖(版本冲突时优先使用 SDK 版本):
kotlin
android {
// 其他基础配置(如 compileSdk、buildToolsVersion 等)
defaultConfig {
// 其他配置(如 applicationId、targetSdk 等)
minSdk = 28 // 必须设置为 28 及以上,否则不支持 SDK
}
// 其他配置(如 buildTypes、compileOptions 等)
}
dependencies {
// 其他项目依赖(如 AndroidX、第三方库等)
// 1. CXR-M SDK 核心依赖
implementation("com.rokid.cxr:client-m:1.0.1-20250812.080117-2")
// 2. SDK 配套依赖(版本冲突时优先使用这些版本)
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.9.3")
implementation("org.jetbrains.kotlin:kotlin-stdlib:2.1.0")
implementation("com.squareup.okio:okio:2.8.0")
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
}

3.权限申请:静态声明与动态校验双保障
CXR-M SDK 依赖网络、Wi-Fi、蓝牙(含定位关联权限)等能力,需完成 静态权限声明 和 动态权限申请,否则 SDK 无法正常使用:
1. 静态权限声明
在项目的 <font style="color:rgba(0, 0, 0, 0.85) !important;background-color:rgba(0, 0, 0, 0);">AndroidManifest.xml</font>
中声明 SDK 所需的 "最小权限集",涵盖网络、Wi-Fi、蓝牙及定位相关权限,代码如下:
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">
<!-- 蓝牙相关权限(含 Android S 及以上新增权限) -->
<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_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 网络与 Wi-Fi 权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<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" />
<application>
<!-- 项目其他配置(如 Activity 注册、Application 声明等) -->
</application>
</manifest>
2. 动态权限申请
Android 6.0(API 23)及以上需动态申请危险权限,CXR-M SDK 使用前必须确保所有必要权限已授予(权限不足会导致 SDK 功能不可用)。以下是基于 <font style="color:rgba(0, 0, 0, 0.85) !important;background-color:rgba(0, 0, 0, 0);">Activity</font>
的动态申请示例:
kotlin
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
private const val REQUEST_CODE_PERMISSIONS = 100 // 权限请求码
// 必要权限列表(区分 Android S/API 31 及以上的新增蓝牙权限)
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()
}
// 用于观察权限申请结果的 LiveData
private val isAllPermissionsGranted = MutableLiveData<Boolean?>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化时发起权限申请
isAllPermissionsGranted.postValue(null)
requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
// 观察权限申请结果,决定是否初始化 SDK
isAllPermissionsGranted.observe(this) { granted ->
when (granted) {
true -> {
// 所有权限已授予,可初始化 CXR-M SDK
}
false -> {
// 部分权限被拒绝,需提示用户开启(否则 SDK 不可用)
}
}
}
}
// 接收权限申请结果
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_PERMISSIONS) {
// 判断所有权限是否均被授予
val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
isAllPermissionsGranted.postValue(allGranted)
}
}
}
户外徒步场景需求拆解与技术方案
1. 前置准备:SDK 导入与户外场景权限优化
(1)SDK 依赖与系统配置优化
依赖配置代码新增高德定位 SDK 和低功耗蓝牙库,设<font style="color:rgba(0, 0, 0, 0.85);">manifestPlaceholders["ble_low_power"] = "true"</font>
;权限代码补充后台定位、存储权限,用弹窗强制引导开启。优化围绕户外场景,保障导航必需的定位能力与设备续航。
kotlin
// build.gradle.kts(模块级)
dependencies {
// CXR-M SDK核心依赖(固定版本,避免户外环境下版本兼容问题)
implementation("com.rokid.cxr:client-m:1.0.1-20250812.080117-2")
// 文档要求的基础依赖(Retrofit、OkHttp等)
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.9.3")
// 新增:户外定位依赖(高德地图SDK)
implementation("com.amap.api:location:5.6.1")
implementation("com.amap.api:map2d:5.2.0")
// 新增:低功耗蓝牙适配库(延长户外设备续航)
implementation("androidx.bluetooth:bluetooth-le:1.1.0")
}
android {
defaultConfig {
minSdk = 28 // 遵循SDK要求
targetSdk = 33
// 新增:户外场景下的蓝牙低功耗配置
manifestPlaceholders["ble_low_power"] = "true"
}
// 优化:户外高温环境下的编译配置(减少资源占用)
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
(2)户外场景权限强化
除文档中的蓝牙、网络、存储权限外,新增定位权限 (导航必需)和后台蓝牙权限(户外远距离时维持连接),并优化动态申请逻辑 ------ 户外场景下,权限拒绝会直接影响安全,需强制引导开启:
kotlin
class OutdoorPermissionManager(private val activity: AppCompatActivity) {
// 户外场景必需权限:定位(导航)+ 蓝牙(连接)+ 存储(离线数据)
private val REQUIRED_PERMISSIONS = mutableListOf(
Manifest.permission.ACCESS_FINE_LOCATION, // 精确定位(徒步路线需要)
Manifest.permission.ACCESS_BACKGROUND_LOCATION, // 后台定位(持续导航)
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.WRITE_EXTERNAL_STORAGE // 存储离线地图
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
add(Manifest.permission.BLUETOOTH_ADVERTISE) // 安卓13+后台蓝牙广播
}
}.toTypedArray()
// 权限申请结果回调
var onPermissionReady: (() -> Unit)? = null
fun requestOutdoorPermissions() {
if (isAllPermissionsGranted()) {
onPermissionReady?.invoke()
return
}
// 发起动态申请,重点提示定位权限的必要性
ActivityResultContracts.RequestMultiplePermissions().launch(
activity,
REQUIRED_PERMISSIONS
) { resultMap ->
val allGranted = resultMap.values.all { it }
if (!allGranted) {
// 户外场景下强制引导:权限不足无法导航,弹出设置页引导
AlertDialog.Builder(activity)
.setTitle("户外导航必需权限")
.setMessage("定位、蓝牙权限用于路线同步和设备连接,拒绝会导致无法使用导航功能,请前往设置开启")
.setCancelable(false) // 不可取消,确保权限开启
.setPositiveButton("去设置") { _, _ ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", activity.packageName, null)
}
activity.startActivityForResult(intent, 1001)
}
.show()
} else {
onPermissionReady?.invoke()
}
}
}
// 检查所有权限是否已授予
private fun isAllPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(activity, it) == PackageManager.PERMISSION_GRANTED
}
// 从设置页返回后重新检查权限
fun onSettingsReturn() {
if (isAllPermissionsGranted()) {
onPermissionReady?.invoke()
} else {
requestOutdoorPermissions() // 仍未开启则重新提示
}
}
}
2. 核心功能实现:户外场景下的设备协同
(1)设备连接优化:户外低功耗蓝牙适配
初始化 SDK 时配置<font style="color:rgba(0, 0, 0, 0.85);">BluetoothMode.BLE_LOW_POWER</font>
和 3 秒重连间隔,扫描时过滤 Rokid Glasses,连接后监听信号(弱信号提示)并同步户外显示参数。通过低功耗与自动重连,解决户外设备距离波动与续航问题。
kotlin
class RokidOutdoorDeviceManager(private val context: Context) {
private lateinit var cxrClient: CxrClient
private var targetDeviceId: String? = null
private var isLowPowerMode = true // 户外默认开启低功耗模式
// 初始化SDK并开启低功耗蓝牙连接
fun initOutdoorClient(
appKey: String,
appSecret: String,
accessKey: String,
onInitResult: ((Boolean) -> Unit)
) {
// 初始化CXR-M SDK,传入户外场景配置(低功耗)
CxrClient.init(
context = context,
appKey = appKey,
appSecret = appSecret,
accessKey = accessKey,
config = CxrConfig.Builder()
.setBluetoothMode(if (isLowPowerMode) BluetoothMode.BLE_LOW_POWER else BluetoothMode.NORMAL)
.setReconnectInterval(3000) // 信号弱时3秒重试一次
.build()
) { initSuccess ->
if (!initSuccess) {
onInitResult.invoke(false)
return@init
}
cxrClient = CxrClient.getInstance()
onInitResult.invoke(true)
}
}
// 扫描并连接Rokid Glasses(户外场景下过滤非徒步用设备)
fun scanAndConnectOutdoorDevice(onConnectResult: ((Boolean, String?) -> Unit)) {
cxrClient.scanDevices(
scanMode = ScanMode.LOW_POWER, // 低功耗扫描,节省电量
filter = { device ->
// 只连接Rokid Glasses(名称含"Rokid Glasses"),排除其他设备
device.name.contains("Rokid Glasses") && device.isOutdoorSupported()
},
callback = object : DeviceScanCallback {
override fun onDeviceFound(device: RokidDevice) {
targetDeviceId = device.deviceId
// 连接时开启"信号强度监听",弱信号时提示用户调整设备位置
cxrClient.connectDevice(
device = device,
signalListener = { rssi ->
if (rssi < -80) { // RSSI低于-80为弱信号
Toast.makeText(context, "蓝牙信号弱,请靠近手机或调整设备位置", Toast.LENGTH_SHORT).show()
}
},
connectCallback = object : ConnectCallback {
override fun onConnected() {
// 连接成功后,同步眼镜的户外显示参数(如亮度)
syncOutdoorDisplayConfig()
onConnectResult.invoke(true, null)
}
override fun onDisconnected() {
onConnectResult.invoke(false, "设备断开连接,正在重试...")
// 自动重连
targetDeviceId?.let { cxrClient.reconnectDevice(it) }
}
override fun onConnectFailed(errorMsg: String) {
onConnectResult.invoke(false, "连接失败:$errorMsg")
}
}
)
}
override fun onScanFailed(errorMsg: String) {
onConnectResult.invoke(false, "扫描设备失败:$errorMsg")
}
}
)
}
// 同步户外显示配置:强光下提高眼镜亮度,适配户外可视性
private fun syncOutdoorDisplayConfig() {
targetDeviceId?.let { deviceId ->
// 调用YodaOS-Sprite的显示接口,设置户外模式(亮度70%,抗眩光)
cxrClient.setDeviceConfig(
deviceId = deviceId,
configType = ConfigType.DISPLAY,
configParams = mapOf(
"brightness" to 70, // 亮度70%(默认50%,户外需提高)
"antiGlare" to true, // 开启抗眩光
"outdoorMode" to true // 开启户外显示优化
),
callback = object : ConfigCallback {
override fun onConfigSuccess() {
Log.d("OutdoorConfig", "眼镜户外显示参数同步成功")
}
override fun onConfigFailed(errorMsg: String) {
Log.e("OutdoorConfig", "显示参数同步失败:$errorMsg")
}
}
)
}
}
// 获取当前眼镜状态(电量、存储)
fun getGlassesStatus(onStatusReady: ((GlassesStatus) -> Unit)) {
targetDeviceId?.let { deviceId ->
cxrClient.getDeviceStatus(
deviceId = deviceId,
statusTypes = listOf(StatusType.BATTERY, StatusType.STORAGE, StatusType.NETWORK),
callback = object : StatusCallback {
override fun onStatusReceived(statusMap: Map<StatusType, Any>) {
val status = GlassesStatus(
battery = statusMap[StatusType.BATTERY] as Int, // 电量百分比
storageLeft = statusMap[StatusType.STORAGE] as Long, // 剩余存储(MB)
networkType = statusMap[StatusType.NETWORK] as String // 网络类型(4G/Wi-Fi)
)
onStatusReady.invoke(status)
}
}
)
}
}
// 眼镜状态数据类
data class GlassesStatus(
val battery: Int, // 电量(0-100)
val storageLeft: Long, // 剩余存储(MB)
val networkType: String // 网络类型
)
}
(2)核心功能 1:路线规划与 AR 同步
代码调用高德地图 SDK 规划离线 / 在线路线,解析后转 JSON,通过 SDK<font style="color:rgba(0, 0, 0, 0.85);">transferData</font>
飞传至眼镜,再调用<font style="color:rgba(0, 0, 0, 0.85);">triggerSceneInteraction</font>
触发 AR 叠加显示。实现手机路线数据到眼镜第一视角指引的流转,核心是 SDK 的数据传输与场景交互能力。
kotlin
class HikingRouteManager(
private val cxrClient: CxrClient,
private val deviceId: String,
private val amapLocationClient: AMapLocationClient
) {
// 规划徒步路线(支持离线模式)
fun planHikingRoute(
startPoint: LatLng, // 起点(手机定位获取)
endPoint: LatLng, // 终点(用户在手机地图选择)
isOffline: Boolean, // 是否离线模式
onRoutePlanned: ((HikingRoute) -> Unit)
) {
// 1. 调用高德地图SDK规划徒步路线
val routeSearch = RouteSearch(context)
val routeQuery = RouteSearch.WalkRouteQuery(
WalkRouteSearchRequest().apply {
origin = RouteSearch.Point(startPoint.longitude, startPoint.latitude)
destination = RouteSearch.Point(endPoint.longitude, endPoint.latitude)
mode = if (isOffline) RouteSearch.MODE_WALK_OFFLINE else RouteSearch.MODE_WALK_ONLINE
}
)
routeSearch.calculateWalkRouteAsyn(routeQuery)
routeSearch.setRouteSearchListener(object : RouteSearch.OnRouteSearchListener {
override fun onWalkRouteSearched(result: WalkRouteResult, errorCode: Int) {
if (errorCode != 1000 || result.walkPaths.isEmpty()) {
Toast.makeText(context, "路线规划失败,请检查地图数据", Toast.LENGTH_SHORT).show()
return
}
// 2. 解析路线数据(提取关键节点:岔路口、转弯点)
val walkPath = result.walkPaths[0]
val routeNodes = walkPath.steps.map { step ->
RouteNode(
latLng = LatLng(step.polyline[0].latitude, step.polyline[0].longitude),
instruction = step.instruction, // 导航指令(如"左转50米")
distance = step.distance // 距离(米)
)
}
val hikingRoute = HikingRoute(
totalDistance = walkPath.distance,
totalTime = walkPath.duration,
nodes = routeNodes
)
// 3. 同步路线数据到AR眼镜
syncRouteToGlasses(hikingRoute)
onRoutePlanned.invoke(hikingRoute)
}
override fun onBusRouteSearched(p0: BusRouteResult?, p1: Int) {}
override fun onDriveRouteSearched(p0: DriveRouteResult?, p1: Int) {}
override fun onRideRouteSearched(p0: RideRouteResult?, p1: Int) {}
})
}
// 同步路线数据到AR眼镜(通过CXR-M SDK飞传JSON格式数据)
private fun syncRouteToGlasses(route: HikingRoute) {
// 1. 将路线数据转为JSON字符串
val routeJson = Gson().toJson(route)
// 2. 用SDK的"数据飞传"功能发送到眼镜(非文件,轻量数据)
cxrClient.transferData(
deviceId = deviceId,
dataType = DataType.ROUTE, // 自定义数据类型:路线
data = routeJson.toByteArray(),
callback = object : DataTransferCallback {
override fun onTransferSuccess() {
Log.d("RouteSync", "路线数据同步到眼镜成功")
// 同步后,触发眼镜的AR显示(调用YodaOS-Sprite场景交互)
triggerGlassesRouteDisplay()
}
override fun onTransferFailed(errorMsg: String) {
Toast.makeText(context, "路线同步失败:$errorMsg", Toast.LENGTH_SHORT).show()
}
}
)
}
// 触发眼镜的AR路线显示(基于YodaOS-Sprite的场景交互)
private fun triggerGlassesRouteDisplay() {
cxrClient.triggerSceneInteraction(
deviceId = deviceId,
sceneType = SceneType.HIKING_NAV, // 徒步导航场景
interactionParams = mapOf("displayMode" to "AR_OVERLAY"), // AR叠加显示
callback = object : SceneCallback {
override fun onInteractionSuccess() {
Toast.makeText(context, "眼镜AR导航已开启", Toast.LENGTH_SHORT).show()
}
override fun onInteractionFailed(errorMsg: String) {
Log.e("ARDisplay", "AR路线显示失败:$errorMsg")
}
}
)
}
// 路线数据类
data class HikingRoute(
val totalDistance: Int, // 总距离(米)
val totalTime: Int, // 总时间(秒)
val nodes: List<RouteNode> // 路线节点
)
// 路线节点数据类
data class RouteNode(
val latLng: LatLng, // 经纬度
val instruction: String, // 导航指令
val distance: Int // 距离下一个节点的距离(米)
)
}
(3)核心功能 2:环境标记与紧急协同
环境标记代码调用 SDK<font style="color:rgba(0, 0, 0, 0.85);">takePhoto</font>
(关美颜、高清模式),回传后关联定位标记;紧急协同代码先<font style="color:rgba(0, 0, 0, 0.85);">startRecording</font>
录 10 秒语音,再拍照,组装数据用<font style="color:rgba(0, 0, 0, 0.85);">transferFile</font>
飞传同伴。依赖 SDK 音视频采集与文件飞传能力,满足户外场景需求。
kotlin
class OutdoorAssistManager(
private val cxrClient: CxrClient,
private val deviceId: String,
private val currentLocation: () -> LatLng // 获取当前手机定位
) {
// 1. 环境标记:眼镜拍照回传手机,标记在地图
fun markEnvironmentPoint(tag: String, onMarkSuccess: ((String) -> Unit)) {
// 调用SDK拍照功能(户外场景:关闭美颜,提高画质用于识别)
cxrClient.takePhoto(
deviceId = deviceId,
photoConfig = PhotoConfig(
beautyMode = false,
resolution = Resolution.HD, // 高清模式,便于看清岔路细节
saveToLocal = true // 同时保存到手机本地
),
callback = object : PhotoCallback {
override fun onPhotoTaken(photoPath: String) {
// 回传手机后,获取当前定位,标记在地图
val location = currentLocation.invoke()
val markMsg = "已标记【$tag】在(${location.latitude}, ${location.longitude})"
Toast.makeText(context, markMsg, Toast.LENGTH_SHORT).show()
onMarkSuccess.invoke(photoPath)
}
override fun onPhotoFailed(errorMsg: String) {
Toast.makeText(context, "环境拍照失败:$errorMsg", Toast.LENGTH_SHORT).show()
}
}
)
}
// 2. 紧急协同:录音+拍照,飞传给同伴设备
fun sendEmergencyHelp(companionDeviceId: String, onHelpSent: ((Boolean) -> Unit)) {
// 第一步:启动录音(10秒,记录求助语音)
cxrClient.startRecording(
deviceId = deviceId,
audioConfig = AudioConfig(
duration = 10, // 固定10秒紧急录音
format = AudioFormat.MP3,
sampleRate = 44100 // 高采样率,确保语音清晰
),
callback = object : RecordingCallback {
override fun onRecordingStopped(audioPath: String) {
// 录音完成后,自动拍照(当前环境)
takeEmergencyPhoto(audioPath, companionDeviceId, onHelpSent)
}
override fun onRecordingFailed(errorMsg: String) {
Toast.makeText(context, "紧急录音失败:$errorMsg", Toast.LENGTH_SHORT).show()
onHelpSent.invoke(false)
}
}
)
}
// 紧急拍照并飞传
private fun takeEmergencyPhoto(
audioPath: String,
companionDeviceId: String,
onHelpSent: ((Boolean) -> Unit)
) {
cxrClient.takePhoto(
deviceId = deviceId,
photoConfig = PhotoConfig(resolution = Resolution.FHD), // 全高清拍照
callback = object : PhotoCallback {
override fun onPhotoTaken(photoPath: String) {
// 组装紧急求助包:照片+录音+当前定位
val helpData = EmergencyHelpData(
photoPath = photoPath,
audioPath = audioPath,
location = currentLocation.invoke(),
timestamp = System.currentTimeMillis()
)
// 飞传给同伴设备
cxrClient.transferFile(
sourceDeviceId = deviceId,
targetDeviceId = companionDeviceId,
filePath = Gson().toJson(helpData).toByteArray(), // 转为JSON文件
fileType = FileType.EMERGENCY_HELP,
callback = object : FileTransferCallback {
override fun onTransferSuccess() {
Toast.makeText(context, "紧急求助已发送", Toast.LENGTH_SHORT).show()
onHelpSent.invoke(true)
}
override fun onTransferFailed(errorMsg: String) {
Toast.makeText(context, "求助发送失败:$errorMsg", Toast.LENGTH_SHORT).show()
onHelpSent.invoke(false)
}
}
)
}
override fun onPhotoFailed(errorMsg: String) {
Toast.makeText(context, "紧急拍照失败:$errorMsg", Toast.LENGTH_SHORT).show()
onHelpSent.invoke(false)
}
}
)
}
// 紧急求助数据类
data class EmergencyHelpData(
val photoPath: String, // 环境照片路径
val audioPath: String, // 求助录音路径
val location: LatLng, // 当前定位
val timestamp: Long // 时间戳
)
}
3. 设备状态监控:户外续航与存储兜底
代码用<font style="color:rgba(0, 0, 0, 0.85);">CoroutineScope(Dispatchers.IO)</font>
定时(1 分钟)调用<font style="color:rgba(0, 0, 0, 0.85);">getGlassesStatus</font>
,电量低于 20%、存储低于 100MB 或无网络时,顶部弹窗预警。通过定时检查与可视化提示,提前规避眼镜断电、存储不足导致的导航中断
kotlin
class OutdoorStatusMonitor(
private val deviceManager: RokidOutdoorDeviceManager,
private val context: Context
) {
private val statusCheckInterval = 60000 // 1分钟检查一次状态
// 启动状态监控
fun startStatusMonitor() {
CoroutineScope(Dispatchers.IO).launch {
while (true) {
delay(statusCheckInterval.toLong())
deviceManager.getGlassesStatus { status ->
// 1. 电量预警:低于20%提示充电
if (status.battery < 20) {
showWarning("眼镜电量不足20%,请尽快充电")
}
// 2. 存储预警:剩余存储低于100MB提示清理
if (status.storageLeft < 100) {
showWarning("眼镜剩余存储不足100MB,建议删除无用照片/录音")
}
// 3. 网络预警:无网络时提示切换离线模式
if (status.networkType == "NONE") {
showWarning("眼镜无网络连接,已自动切换到离线导航模式")
}
}
}
}
}
// 显示预警弹窗(户外场景:弹窗不遮挡导航界面,放在顶部)
private fun showWarning(message: String) {
val toast = Toast.makeText(context, message, Toast.LENGTH_LONG)
toast.setGravity(Gravity.TOP, 0, 100) // 顶部显示,避免遮挡地图
toast.show()
}
}
总结
本文基于 CXR-M SDK 落地户外徒步协同导航系统,解决四大痛点,凸显 SDK "场景连接器" 价值。同时沉淀户外开发要点:优先做环境适配,场景化调优 SDK 功能,完善风险兜底机制。还分享避坑逻辑,且系统架构可复用到多户外场景。开发者学习 SDK 不应局限接口调用,需结合场景痛点转化价值,本文经验助力开发者少走弯路,快速落地高质量 AR 协同应用。