跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Kotlin大前端java

基于 Rokid AR 眼镜的会议纪要助手开发实践

记录基于 Rokid AR 眼镜开发会议纪要助手的完整过程。针对会议时间管理痛点,采用手机端控制加眼镜端显示的架构。使用 Kotlin 语言和 Rokid CXR-M SDK,实现蓝牙连接、议程数据同步及实时计时功能。文章涵盖项目配置、数据模型设计、SDK 封装、主界面逻辑及常见踩坑经验(如蓝牙两阶段连接、后台计时误差、中文乱码等),为 AR 办公场景应用开发提供参考。

暗影行者发布于 2026/4/6更新于 2026/6/1342 浏览
基于 Rokid AR 眼镜的会议纪要助手开发实践

戴在眼前的议程管家:基于 Rokid AR 眼镜的会议纪要助手开发实录

'李总,需求评审环节已经超时 12 分钟了,后面的自由讨论时间不够了……'

相信每个经常主持或参与会议的人都经历过这样的尴尬:一个议题讨论过于热烈,时间悄然流逝,等到发现时,整个会议日程已经被打乱。手机上的计时器?太容易被忽略。电脑上的提醒?开会时你根本不会盯着屏幕看。

如果能在眼前实时看到当前议题、已用时间、超时警告呢?这就是我开发这款会议纪要助手的初衷——把议程管理"戴"在眼前。

本文将从零开始,完整记录基于 Rokid CXR-M SDK 开发这款 AR 会议助手的全过程,涵盖技术选型、架构设计、核心代码实现与踩坑经验。

一、为什么是 AR 眼镜?

1.1 传统方案的困境

在正式开发之前,我调研了市面上常见的会议管理工具:

方案问题
手机计时 App需要频繁解锁查看,打断会议节奏
电脑倒计时主持人注意力在屏幕,而非与会者
人工报时需要专人负责,且容易忘记
投影时钟只有演讲者能看到,主持人无法兼顾

这些方案的共同问题是:信息获取需要主动动作。主持人要么低头看手机,要么转头看屏幕,这在某种程度上都会分散注意力,影响会议的流畅性。

1.2 AR 眼镜的优势

AR 眼镜提供了一个独特的交互场景:抬眼即见,无需分心。

  • 被动信息获取:信息直接出现在视野中,不需要主动去"找"
  • 自然交互:主持人可以保持与与会者的眼神交流
  • 实时提醒:时间节点可以通过语音或视觉主动推送
  • 专注主持:不被设备操作打断会议节奏

这正是 Rokid CXR-M SDK 提供的"提词器场景"的天然应用——把会议议程像提词器一样展示在眼前。

二、系统架构设计

2.1 整体架构

系统采用经典的手机端控制 + 眼镜端显示的架构:

文章配图

2.2 技术选型
  • 开发语言:Kotlin(简洁、安全、与 Android 深度集成)
  • 最低 SDK:Android 9 (API 28),支持绝大多数现代设备
  • 核心依赖:Rokid CXR-M SDK 1.0.1
  • UI 框架:传统 ViewBinding + XML 布局(简单直接)

三、从零开始:项目配置

3.1 依赖配置

首先,在项目级 settings.gradle.kts 中添加 Rokid Maven 仓库:

repositories {
    maven { url = uri("https://maven.rokid.com/repository/maven-public/") }
    google()
    mavenCentral()
}

文章配图

然后在 app/build.gradle.kts 中添加 SDK 依赖:

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}
android {
    namespace = "com.rokid.meeting"
    compileSdk = 34
    defaultConfig {
        applicationId = "com.rokid.meetinghelper"
        minSdk = 28
        targetSdk = 34
        versionCode = 1
        versionName = "1.0.0"
    }
    buildTypes {
        release {
            isMinifyEnabled = false
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
}
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("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    implementation("androidx.cardview:cardview:1.0.0")
}

文章配图

3.2 权限配置

眼镜通过蓝牙连接,需要声明相应的蓝牙权限。在 AndroidManifest.xml 中:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
    <!-- 蓝牙基础权限 -->
    <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" tools:targetApi="s" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <application ...>
        <activity android:name=".MainActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

注意:BLUETOOTH_SCAN 添加了 neverForLocation 标志,因为我们只需要扫描蓝牙设备,不需要获取位置信息。这样可以简化权限申请流程。

四、核心模块一:数据模型

好的数据模型是清晰代码的基础。我们定义两个核心数据类:Meeting(会议)和 AgendaItem(议程项)。

4.1 数据类定义

文章配图

// Meeting.kt
package com.rokid.meeting.data

/**
 * 会议数据模型
 * @param id 会议唯一标识
 * @param title 会议标题
 * @param startTime 会议开始时间戳
 * @param totalTime 预计总时长(分钟)
 * @param agenda 议程项列表
 */
data class Meeting(
    val id: Int,
    val title: String,
    val startTime: Long,
    val totalTime: Int,
    val agenda: List<AgendaItem>
)

/**
 * 议程项数据模型
 * @param index 议程序号(用于排序显示)
 * @param title 议题标题
 * @param speaker 主讲人(可为空,表示自由讨论环节)
 * @param duration 预计时长(分钟)
 * @param notes 备注(如"需提前准备文档")
 */
data class AgendaItem(
    val index: Int,
    val title: String,
    val speaker: String?,
    val duration: Int,
    var notes: String? = null
)
4.2 预设会议数据

为了演示方便,我预设了一个典型的产品周会模板:

object MeetingData {
    val meetings = listOf(
        Meeting(
            id = 1,
            title = "产品周会",
            startTime = System.currentTimeMillis(),
            totalTime = 60,
            agenda = listOf(
                AgendaItem(1, "上周工作回顾", "张三", 10, null),
                AgendaItem(2, "本周计划", "李四", 15, null),
                AgendaItem(3, "需求评审", "王五", 20, "需提前准备文档"),
                AgendaItem(4, "自由讨论", null, 15, null)
            )
        )
    )
}

这个数据结构的设计考虑了几个实际场景:

  • speaker 可为空,支持"自由讨论"这类没有固定主讲人的环节
  • notes 字段用于记录提醒事项,比如"需提前准备文档"
  • index 与列表位置分离,方便后续扩展议程排序功能

五、核心模块二:SDK 封装与眼镜通信

这是整个项目最核心的部分——如何与 Rokid 眼镜建立连接并发送数据。我将 CXR-M SDK 的功能封装成 RokidGlassesManager 单例对象。

5.1 SDK 初始化与连接

文章配图

// RokidGlassesManager.kt
package com.rokid.meeting.sdk

import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import com.rokid.cxr.api.CxrApi
import com.rokid.cxr.callback.BluetoothStatusCallback
import com.rokid.cxr.util.ValueUtil

object RokidGlassesManager {
    // CXR-M API 实例,懒加载
    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)
    }
    
    interface SendCallback {
        fun onSuccess()
        fun onFailed(errorMsg: String)
    }
    
    val isConnected: Boolean get() = cxrApi.isBluetoothConnected
}
5.2 查找并连接眼镜
/**
 * 从已配对设备中查找 Rokid 眼镜
 * 注意:眼镜需要先在系统蓝牙设置中配对
 */
fun findRokidGlasses(bluetoothAdapter: BluetoothAdapter): BluetoothDevice? {
    if (ActivityCompat.checkSelfPermission(
            bluetoothAdapter.javaClass, Manifest.permission.BLUETOOTH_CONNECT
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        return null
    }
    return bluetoothAdapter.bondedDevices.find {
        it.name?.contains("Rokid", ignoreCase = true) || it.name?.contains("Glasses", ignoreCase = true)
    }
}

/**
 * 连接眼镜
 * 连接过程分两步:
 * 1. initBluetooth() 初始化蓝牙连接,获取连接信息
 * 2. connectBluetooth() 使用连接信息完成最终连接
 */
fun connectGlasses(context: Context, device: BluetoothDevice) {
    connectionCallback?.onConnecting()
    cxrApi.initBluetooth(context, device, object : BluetoothStatusCallback() {
        override fun onConnectionInfo(
            socketUuid: String?,
            mac: String?,
            rokidAccount: String?,
            glassesType: Int
        ) {
            if (!socketUuid.isNullOrEmpty() && !mac.isNullOrEmpty()) {
                connectBluetooth(context, socketUuid, mac)
            } else {
                connectionCallback?.onFailed("获取连接信息失败")
            }
        }
        override fun onConnected() {
            connectionCallback?.onConnected()
        }
        override fun onDisconnected() {
            connectionCallback?.onDisconnected()
        }
        override fun onFailed(errorCode: ValueUtil.CxrBluetoothErrorCode?) {
            connectionCallback?.onFailed(errorCode?.name ?: "连接失败")
        }
    })
}

private fun connectBluetooth(context: Context, socketUuid: String, macAddress: String) {
    cxrApi.connectBluetooth(context, socketUuid, macAddress, object : BluetoothStatusCallback() {
        override fun onConnected() {
            Log.d("RokidGlassesManager", "蓝牙连接确认成功")
        }
        override fun onDisconnected() {
            connectionCallback?.onDisconnected()
        }
        override fun onFailed(errorCode: ValueUtil.CxrBluetoothErrorCode?) {
            connectionCallback?.onFailed(errorCode?.name ?: "连接失败")
        }
        override fun onConnectionInfo(
            socketUuid: String?,
            macAddress: String?,
            rokidAccount: String?,
            glassesType: Int
        ) {
            // 此回调在 connectBluetooth 中不会触发
        }
    })
}

踩坑经验:CXR-M SDK 的蓝牙连接分为两阶段——先通过 initBluetooth() 初始化并获取连接参数(socketUuid 和 macAddress),再通过 connectBluetooth() 完成真正的连接。直接调用 initBluetooth() 后以为连接成功,结果发送数据时一直失败,就是这个原因。

5.3 发送议程到眼镜
/**
 * 发送当前议程到眼镜
 * 使用 CXR-M SDK 的提词器场景(WORD_TIPS)
 */
fun sendAgenda(meeting: Meeting, currentIndex: Int, callback: SendCallback? = null): Boolean {
    if (!isConnected) {
        callback?.onFailed("眼镜未连接")
        return false
    }
    val item = meeting.agenda.getOrNull(currentIndex) ?: return false
    val text = buildDisplayText(meeting, item, currentIndex)
    
    // 1. 激活提词器场景
    cxrApi.controlScene(ValueUtil.CxrSceneType.WORD_TIPS, true, null)
    
    // 2. 发送文本内容
    val status = cxrApi.sendStream(
        type = ValueUtil.CxrStreamType.WORD_TIPS,
        stream = text.toByteArray(Charsets.UTF_8),
        fileName = "agenda.txt",
        cb = object : SendStatusCallback() {
            override fun onSendSucceed() {
                callback?.onSuccess()
            }
            override fun onSendFailed(errorCode: ValueUtil.CxrSendErrorCode?) {
                callback?.onFailed(errorCode?.name ?: "发送失败")
            }
        }
    )
    return status == ValueUtil.CxrStatus.REQUEST_SUCCEED
}

fun disconnect() {
    cxrApi.deinitBluetooth()
}
5.4 构建显示文本

眼镜端显示的内容需要精心设计——信息要全面,但不能过于拥挤:

private fun buildDisplayText(meeting: Meeting, item: AgendaItem, currentIndex: Int): String {
    return buildString {
        appendLine("📋 ${meeting.title}")
        appendLine()
        appendLine("────── 当前议题 ──────")
        appendLine()
        appendLine("${currentIndex + 1}. ${item.title}")
        appendLine()
        item.speaker?.let { appendLine("主讲:$it") }
        appendLine("预计:${item.duration}分钟")
        item.notes?.let {
            appendLine()
            appendLine("📝 $it")
        }
    }
}

眼镜端的显示效果:

┌──────────────────────────────┐
│ 📋 产品周会                   │
│                              │
│ ────── 当前议题 ──────         │
│                              │
│ 3. 需求评审                  │
│                              │
│ 主讲:王五                   │
│ 预计:20 分钟                 │
│                              │
│ 📝 需提前准备文档             │
└──────────────────────────────┘

六、核心模块三:主界面与业务逻辑

6.1 Activity 结构

主界面是整个应用的控制中心,负责会议流程的管理和眼镜通信的触发:

// MainActivity.kt
package com.rokid.meeting

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private var currentMeeting: Meeting? = null
    private var currentAgendaIndex = 0
    private var startTime: Long = 0
    private var timer: java.util.Timer? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setSupportActionBar(binding.toolbar)
        supportActionBar?.title = "会议纪要助手"
        checkPermissions()
        setupButtons()
        observeConnection()
    }
}
6.2 按钮事件绑定
private fun setupButtons() {
    // 连接/断开眼镜
    binding.btnConnect.setOnClickListener {
        if (RokidGlassesManager.isConnected) {
            RokidGlassesManager.disconnect()
            updateConnectionStatus()
        } else {
            connectGlasses()
        }
    }
    
    // 开始会议
    binding.btnStart.setOnClickListener { startMeeting() }
    
    // 上一个议题
    binding.btnPrev.setOnClickListener { previousAgenda() }
    
    // 下一个议题
    binding.btnNext.setOnClickListener { nextAgenda() }
    
    // 发送到眼镜
    binding.btnSend.setOnClickListener { sendToGlasses() }
}
6.3 计时器实现

计时是会议管理的核心功能。这里采用基于系统时间计算的方式,避免定时器累积误差:

private fun startMeeting() {
    currentMeeting = MeetingData.meetings[0]
    currentAgendaIndex = 0
    startTime = System.currentTimeMillis()
    startTimer()
    updateDisplay()
}

private fun startTimer() {
    val meeting = currentMeeting ?: return
    timer?.cancel()
    timer = java.util.Timer()
    timer?.scheduleAtFixedRate(object : TimerTask() {
        override fun run() {
            val elapsed = (System.currentTimeMillis() - startTime) / 1000
            val minutes = elapsed / 60
            val seconds = (elapsed % 60).toInt()
            runOnUiThread {
                binding.tvElapsed.text = "已进行 ${minutes}分${seconds}秒"
            }
        }
    }, 0, 1000)
}

为什么不用累加计时?

很多初学者会这样实现计时器:

// 错误示范:累加计时
var seconds = 0
timer.scheduleAtFixedRate({
    seconds++
    updateDisplay(seconds)
}, 1000)

这种实现的问题是:如果手机进入低电量模式或后台运行,定时器可能会被系统暂停或变慢,导致计时不准确。而基于 System.currentTimeMillis() 的计算,无论定时器是否精确,显示的时间永远是准确的。

6.4 议程切换
private fun previousAgenda() {
    currentMeeting?.let { meeting ->
        if (currentAgendaIndex > 0) {
            currentAgendaIndex--
            startTime = System.currentTimeMillis()
            timer?.cancel()
            startTimer()
            updateDisplay()
        }
    }
}

private fun nextAgenda() {
    currentMeeting?.let { meeting ->
        if (currentAgendaIndex < meeting.agenda.size - 1) {
            currentAgendaIndex++
            startTime = System.currentTimeMillis()
            timer?.cancel()
            startTimer()
            updateDisplay()
        }
    }
}

private fun updateDisplay() {
    val meeting = currentMeeting ?: return
    val item = meeting.agenda.getOrNull(currentAgendaIndex) ?: return
    binding.apply {
        tvTitle.text = meeting.title
        tvCurrentAgenda.text = item.title
        item.speaker?.let { binding.tvSpeaker.text = "发言人:$it" }
        binding.tvDuration.text = "预计 ${item.duration} 分钟"
        binding.tvPage.text = "议题 ${currentAgendaIndex + 1}/${meeting.agenda.size}"
    }
}
6.5 发送到眼镜
private fun sendToGlasses() {
    if (!RokidGlassesManager.isConnected) {
        Toast.makeText(this, "请先连接眼镜", Toast.LENGTH_SHORT).show()
        return
    }
    val meeting = currentMeeting ?: return
    RokidGlassesManager.sendAgenda(meeting, currentAgendaIndex, object : RokidGlassesManager.SendCallback {
        override fun onSuccess() {
            runOnUiThread {
                Toast.makeText(this@MainActivity, "已发送到眼镜", Toast.LENGTH_SHORT).show()
            }
        }
        override fun onFailed(errorMsg: String) {
            runOnUiThread {
                Toast.makeText(this@MainActivity, errorMsg, Toast.LENGTH_SHORT).show()
            }
        }
    })
}
6.6 蓝牙连接处理
private fun connectGlasses() {
    val adapter = BluetoothAdapter.getDefaultAdapter
    if (adapter == null || !adapter.isEnabled) {
        Toast.makeText(this, "请开启蓝牙", Toast.LENGTH_SHORT).show()
        return
    }
    val device = RokidGlassesManager.findRokidGlasses(adapter)
    if (device == null) {
        Toast.makeText(this, "未找到眼镜,请先配对", Toast.LENGTH_SHORT).show()
        return
    }
    RokidGlassesManager.connectGlasses(this, device)
}

private fun observeConnection() {
    RokidGlassesManager.setConnectionCallback(
        object : RokidGlassesManager.ConnectionCallback {
            override fun onConnecting() {
                runOnUiThread {
                    binding.btnConnect.text = "连接中..."
                }
            }
            override fun onConnected() {
                runOnUiThread {
                    binding.btnConnect.text = "断开连接"
                    Toast.makeText(this@MainActivity, "眼镜已连接", Toast.LENGTH_SHORT).show()
                }
            }
            override fun onDisconnected() {
                runOnUiThread {
                    binding.btnConnect.text = "连接眼镜"
                }
            }
            override fun onFailed(errorMsg: String) {
                runOnUiThread {
                    binding.btnConnect.text = "连接眼镜"
                    Toast.makeText(this@MainActivity, errorMsg, Toast.LENGTH_SHORT).show()
                }
            }
        }
    )
}
6.7 权限检查

Android 12+ 对蓝牙权限做了细分,需要动态申请:

private fun checkPermissions() {
    val permissions = mutableListOf<String>()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        permissions.add(Manifest.permission.BLUETOOTH_SCAN)
        permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
    }
    val notGranted = permissions.filter {
        ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
    }
    if (notGranted.isNotEmpty()) {
        ActivityCompat.requestPermissions(this, notGranted.toTypedArray(), 100)
    }
}

七、开发中的踩坑与解决方案

7.1 问题一:连接成功但发送失败

现象:initBluetooth() 回调了 onConnected(),但调用 sendStream() 时返回失败。

原因:SDK 的蓝牙连接是两阶段的,initBluetooth() 只是初始化阶段,还需要调用 connectBluetooth() 完成真正的连接。

解决:在 onConnectionInfo() 回调中获取 socketUuid 和 macAddress,然后调用 connectBluetooth()。

7.2 问题二:计时器在后台不准确

现象:手机锁屏后再打开,计时器显示的时间明显偏少。

原因:系统为了省电会限制后台应用的定时器执行频率。

解决:使用 System.currentTimeMillis() 计算已用时间,而不是累加计数器。这样即使定时器不精确,显示的时间也是准确的。

7.3 问题三:眼镜显示中文乱码

现象:发送的中文在眼镜端显示为乱码。

原因:发送数据时没有指定正确的字符编码。

解决:明确使用 UTF-8 编码:

stream = text.toByteArray(Charsets.UTF_8)
7.4 问题四:蓝牙权限被拒绝

现象:在 Android 12+ 设备上,应用启动时直接崩溃。

原因:Android 12 新增了 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT 权限,需要动态申请。

解决:在 onCreate() 中检查并申请权限,同时在 AndroidManifest.xml 中声明。

八、功能演示

8.1 功能清单
功能说明状态
蓝牙连接查找并连接 Rokid 眼镜✅
会议管理预设会议模板✅
议程控制上一个/下一个切换✅
实时计时精确到秒的计时显示✅
眼镜同步议程内容发送到眼镜✅
超时提醒TTS 语音提醒🔜
会议纪要AI 自动生成🔜
8.2 使用流程
  1. 准备阶段:在手机蓝牙设置中配对 Rokid 眼镜
  2. 启动应用:打开会议纪要助手,授予蓝牙权限
  3. 连接眼镜:点击"连接眼镜"按钮
  4. 开始会议:点击"开始会议",计时自动开始
  5. 切换议题:使用"上一个""下一个"按钮切换议程
  6. 同步眼镜:点击"发送到眼镜",当前议程显示在眼镜上
  7. 会议结束:断开眼镜连接,退出应用

九、总结与展望

9.1 项目总结

这个项目虽然功能相对简单,但完整地展示了 AR 眼镜应用的开发流程:

  1. 理解场景:从用户痛点出发,找到 AR 眼镜的真正价值点
  2. SDK 集成:学习 CXR-M SDK 的 API,理解其设计思想
  3. 架构设计:合理分层,将 SDK 封装与业务逻辑解耦
  4. 细节打磨:处理好权限、编码、计时等细节问题
9.2 后续规划

当前版本还只是一个 MVP(最小可行产品),后续计划增加以下功能:

  • 超时语音提醒:当议题超时时,通过眼镜 TTS 发出语音提醒
  • 会议纪要生成:接入语音识别,自动生成会议纪要
  • 云端同步:会议记录上传云端,支持多设备查看
  • 多人协作:支持多副眼镜同时连接,参会者都能看到议程
9.3 关于 AR 办公的思考

AR 眼镜在办公场景有着巨大的想象空间。会议管理只是一个小切口,类似的场景还有:

  • 演讲提词器:演讲者眼前实时显示台词
  • 培训指导:操作步骤直接叠加在视野中
  • 远程协作:专家远程标注,本地实时可见
  • 信息展示:会议室、工位的信息卡片

期待更多开发者加入 AR 生态,一起探索这个新的人机交互边界。

目录

  1. 戴在眼前的议程管家:基于 Rokid AR 眼镜的会议纪要助手开发实录
  2. 一、为什么是 AR 眼镜?
  3. 1.1 传统方案的困境
  4. 1.2 AR 眼镜的优势
  5. 二、系统架构设计
  6. 2.1 整体架构
  7. 2.2 技术选型
  8. 三、从零开始:项目配置
  9. 3.1 依赖配置
  10. 3.2 权限配置
  11. 四、核心模块一:数据模型
  12. 4.1 数据类定义
  13. 4.2 预设会议数据
  14. 五、核心模块二:SDK 封装与眼镜通信
  15. 5.1 SDK 初始化与连接
  16. 5.2 查找并连接眼镜
  17. 5.3 发送议程到眼镜
  18. 5.4 构建显示文本
  19. 六、核心模块三:主界面与业务逻辑
  20. 6.1 Activity 结构
  21. 6.2 按钮事件绑定
  22. 6.3 计时器实现
  23. 6.4 议程切换
  24. 6.5 发送到眼镜
  25. 6.6 蓝牙连接处理
  26. 6.7 权限检查
  27. 七、开发中的踩坑与解决方案
  28. 7.1 问题一:连接成功但发送失败
  29. 7.2 问题二:计时器在后台不准确
  30. 7.3 问题三:眼镜显示中文乱码
  31. 7.4 问题四:蓝牙权限被拒绝
  32. 八、功能演示
  33. 8.1 功能清单
  34. 8.2 使用流程
  35. 九、总结与展望
  36. 9.1 项目总结
  37. 9.2 后续规划
  38. 9.3 关于 AR 办公的思考
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 面向无人机和智能手机的 YOLOv8 模型改进实战—棉叶病虫害多尺度目标检测
  • 算法刷题:替换所有问号与提莫攻击
  • Antigravity 上手指南:打造 VS Code 风格的 AI IDE
  • Python 第三方库实战:键盘监听鼓励器与学生管理系统
  • JVM 垃圾回收核心:根可达性、三色标记与 CMS/G1 对比
  • 在 Linux 服务器上部署 Clawdbot 并对接 Telegram 机器人
  • Windows 下 Claude Code 依赖修复:Git Bash 环境变量配置详解
  • AI 浪潮下的前端演进与跨端实战指南
  • JavaEE 深度解析:从 Jakarta EE 演进、B/S 架构到 SSM 框架
  • LazyLLM 多 Agent 应用实践:从源码部署到可视化 Web 调试
  • 华为预训练大模型白皮书核心观点与技术趋势分析
  • FPGA SPI Flash配置模式:从硬件设计到约束文件的隐形桥梁
  • Python 基础入门:数据类型、运算符与文件处理
  • Python 从零构建 AI 多智能体系统:三方协作实现复杂任务
  • iOS 网络安全实战:HTTPS 通信与证书锁定防抓包
  • AIGC 技术在元宇宙与虚拟世界中的应用
  • 大模型(LLM)定义及其与人工智能的关系解析
  • ChatGPT 降低 AIGC 率指令实战:从原理到最佳实践
  • MiniMax-M2.5 开源发布:编程与智能体性能解析
  • C++ 基于正倒排索引的 Boost 搜索引擎实战

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online