ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

uniapp跨平台蓝牙开发从零到上架:BLE应用避坑指南

uniapp跨平台蓝牙开发从零到上架:BLE应用避坑指南 简介面向uni-app开发者的跨平台蓝牙演示资源包基于Vue.js多端框架实现应用内自动开启蓝牙、设备扫描连接与数据读写能力可服务智能硬件、健康监测、智能家居等蓝牙交互场景也适用于同类功能快速原型验证。包内共43个文件压缩后约3.72MB主要包括JavaScript业务逻辑、CSS样式、JSON配置、TTF字体、APK安装包与PNG界面素材其中PNG截图可辅助理解页面流程APK包便于在真机直接体验整体目录结构清晰便于导入工程后按模块查找与修改。已有1210人浏览学习适合具备一定前端基础、希望快速移植蓝牙能力的APP开发者。通过示例可掌握蓝牙插件接入流程、蓝牙状态检测与自动开启、设备扫描连接、特征值读写等关键环节同时理解不同平台兼容适配、异常处理与用户交互设计思路为后续真实业务开发提供可复用模块参考。1. 为什么说 uniapp 是跨平台蓝牙 demo 的最快落地路径做蓝牙类 App 开发的人多半被这组问题折磨过Android 有经典蓝牙和 BLE 两套 APIiOS 只开放 BLE 却对后台扫描各种限制一个功能要写两套原生逻辑联调时还要来回改包名和权限描述。而uniapp在蓝牙这点上的价值不是「少写代码」而是把uni.openBluetoothAdapter到uni.writeBLECharacteristicValue这一整条链路统一成一套事件模型H5、App、小程序共用一套蓝牙业务逻辑。标题里的跨平台蓝牙 demo 听起来像是个玩具工程但真把它跑通、跑稳背后涉及权限配置、缓存处理、分包策略和机型适配值得系统梳理一遍。这篇博文会直接给你一份能编译、能扫描、能收发数据的 uniapp 蓝牙工程骨架从基础概念讲到真机调试中的坑最后落到如何把 demo 改造成可上架的工程。2. uni 蓝牙 API 的能力边界与 manifest 配置先认清再动手2.1 弄清 uni 蓝牙 API 是基于原生蓝牙的封装不是 H5 的 Web Bluetooth常见误解是uniapp 的蓝牙能力来自 H5 的 navigator.bluetooth。实际上 uniapp 在 App 端走的是 Native.js 封装的原生蓝牙框架在小程序端走的是微信/各家小程序的蓝牙协议在 H5 端则依赖浏览器是否支持 Web Bluetooth而 iOS 上的 Safari 至今不支持Android Chrome 支持但权限逻辑激烈收紧。这意味着你在 uniapp 里写的蓝牙代码真正可跨的是「App 小程序」这两个端H5 端只能做个降级提示和扫码替代方案。做 demo 之前在项目文档里先写清这条能力边界能省掉后面一多半的排错时间。2.2 manifest.json 里的 Bluetooth 权限配置缺一项就静默失败uniapp 的权限配置集中在 manifest.json 的app-plus.distribute节点下Android 端需要申请蓝牙权限iOS 需要声明用途描述。一个常见隐蔽坑只配置了BLUETOOTH权限Android 12 以上系统要求BLUETOOTH_SCAN和BLUETOOTH_CONNECT这类运行时权限否则扫描回调里一直返回空数组且不报错。iOS 端虽然 uniapp 会在调用 API 时自动弹权限框但NSBluetoothAlwaysUsageDescription描述必须提前写好否则首次调用会直接崩溃。{ app-plus: { distribute: { android: { permissions: [ uses-permission android:name\android.permission.BLUETOOTH\ /, uses-permission android:name\android.permission.BLUETOOTH_ADMIN\ /, uses-permission android:name\android.permission.BLUETOOTH_SCAN\ /, uses-permission android:name\android.permission.BLUETOOTH_CONNECT\ /, uses-permission android:name\android.permission.ACCESS_FINE_LOCATION\ / ] }, ios: { privacyDescription: { NSBluetoothAlwaysUsageDescription: 需要使用蓝牙连接设备进行数据传输 } } } } }这段配置直接影响整个 demo 在真机上的行为但有一个细节极易忽略Android 的定位权限。因为蓝牙扫描在 Android 系统中被归类为“设备附近的连接”从 Android 6 开始扫描 BLE 设备时必须持有定位权限否则扫描接口永远返回空数组。另一个细节是BLUETOOTH_SCAN和BLUETOOTH_CONNECT这类新权限只对 Android 12 以上生效低版本机型不会识别它们所以旧权限必须保留不能为了追赶新写法而删除正确的做法是新旧权限同时声明运行时再做版本判断。2.3 uni.openBluetoothAdapter 返回 10001 或者 10002多半是系统设置问题初始化蓝牙适配器是整个流程的第一步也是最容易翻车的一步。uni.openBluetoothAdapter如果返回10001蓝牙未打开或10002系统不支持蓝牙你无法通过代码强制打开蓝牙只能引导用户去系统设置里开启。这里有一个实用模式把初始化逻辑写成带重试的 Promise 封装并在失败时弹窗引导因为用户在 Android 通知栏里点击开启蓝牙后应用不会自动触发回调需要监听uni.onBluetoothAdapterStateChange事件来做状态恢复后的自动重连。function initBluetooth() { return new Promise((resolve, reject) { uni.openBluetoothAdapter({ success: (res) { console.log(蓝牙适配器初始化成功, res); resolve(res); }, fail: (err) { console.error(蓝牙适配器初始化失败, err.errCode, err.errMsg); if (err.errCode 10001) { uni.showModal({ title: 提示, content: 请打开系统蓝牙后再试, success: () initBluetooth().then(resolve).catch(reject) }); } else { reject(err); } } }); }); }这段代码里有个细节值得说明initBluetooth函数内部在fail分支再次调用自身目的是让用户在系统设置页开启蓝牙后回到应用时无需重启页面就能重新建立蓝牙会话。实际开发时这种自递归要加一个重试次数上限比如超过 3 次就直接失败只给用户一个错误码避免用户反复点确认造成死循环。另外初始化成功后建议紧接着做一次uni.getBluetoothAdapterState把当前蓝牙状态缓存到全局 store后续扫描逻辑都要先读这个状态判断是否需要重新初始化因为用户可能在应用的蓝牙操作中途系统级关闭蓝牙。3. 扫描、连接、读写特征值BLE 最小闭环怎么写3.1 startBluetoothDevicesDiscovery扫描参数决定能搜到多少设备uni.startBluetoothDevicesDiscovery用来启动扫描和原生 Android 的startScan不同uniapp 的扫描是「按需开启、持续回调」机制需要主动调用uni.stopBluetoothDevicesDiscovery才会停止。在 demo 里最容易犯的错是一进去就无脑扫描结果每收到一个广播包回调就会触发一次页面 setData直接导致列表卡顿。function startScan() { uni.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, interval: 0, services: [], success: () { console.log(扫描启动成功); }, fail: (err) { console.error(启动扫描失败, err); } }); } // 在页面 onLoad 里监听设备发现 uni.onBluetoothDeviceFound((res) { const devices res.devices; devices.forEach((device) { const deviceId device.deviceId; const rssi device.RSSI; const name device.name || device.localName || 未知设备; console.log(发现设备: ${name}, deviceId: ${deviceId}, 信号强度: ${rssi}dBm); }); });这里面的三组参数值得深入allowDuplicatesKey设为false时同一个设备的广播包只会上报一次适合做设备列表设为true则会重复上报同一设备的新 RSSI 值适合做信号测距services是过滤条件以蓝牙模块常用的 16 位 UUID 字符串或 128 位完整 UUID 数组传入能大幅减少无效广播包的数量但这需要提前知道模块的服务 UUIDinterval是重复上报的间隔单位毫秒设为 0 表示系统默认频率。还有一个容易被忽略的约束扫描过程中如果进入后台System 可能在 10 秒内暂停底层扫描从后台回前台后需要重新启动扫描这种中断在 demo 阶段不会立刻暴露但在室外实测时会突然发现列表不更新了。3.2 createBLEConnection 之外必须监听连接状态丢失扫描到设备后通过uni.createBLEConnection建立连接但连接失败的原因千奇百怪设备已被其他 App 占用、广播里带的是随机地址、模块处于不可连接状态、Android 端蓝牙栈缓存了旧连接。所以连接代码要放在一个超时保护的容器里并监听uni.onBLEConnectionStateChange。function connectDevice(deviceId) { return new Promise((resolve, reject) { let timer setTimeout(() { reject(new Error(连接超时)); }, 10000); uni.createBLEConnection({ deviceId, timeout: 10000, success: () { clearTimeout(timer); // 连接成功后立即获取服务列表 uni.getBLEDeviceServices({ deviceId, success: (res) { const services res.services; console.log(获取到服务列表, services.map(s s.uuid)); resolve(services); }, fail: (err) reject(err) }); }, fail: (err) { clearTimeout(timer); reject(err); } }); uni.onBLEConnectionStateChange((res) { if (res.connected false) { console.warn(设备 ${res.deviceId} 连接已断开); clearTimeout(timer); reject(new Error(连接已断开)); } }); }); }超时保护的实际意义是防止被一个不可达设备锁死整个应用线程。timeout参数虽然 uniapp 在部分平台上支持但 Android 底层并不总把它当超时用所以应用中再包一层 setTimeout 是保险做法。另一个细节是getBLEDeviceServices必须在连接成功回调后再调不能在createBLEConnection的 success 中并发调用多个服务发现否则会导致底层 BLE 栈状态冲突出现fail: 系统错误的诡异报错。如果连接失败返回的错误码是10012说明连接超时是10006表示设备不可用这两种情况建议都用震动加 toast 双重提醒因为用户大概率不在看屏幕。3.3 特征值读写与 notify 监听demo 数据传输的主战场拿到服务列表后需要从其中筛出可读、可写、可通知的特征值。很多人在这里会被 BLE 的层级关系搞晕一个设备有多个 Service每个 Service 有多个 Characteristic每个 Characteristic 有 properties读、写、通知。把这三层关系理清了数据传输才是真正的可控状态。function findReadWriteCharacteristic(services) { for (const service of services) { // 获取每个服务下的特征值列表 uni.getBLEDeviceCharacteristics({ deviceId: this.deviceId, serviceId: service.uuid, success: (res) { const characteristics res.characteristics; for (const char of characteristics) { const props char.properties; if (props.write props.read) { console.log(找到可读写特征值, char.uuid); this.serviceId service.uuid; this.characteristicId char.uuid; return; } } }, fail: (err) { console.error(获取特征值失败, err); } }); } } function writeData(data) { const buffer new Uint8Array(data.length); data.forEach((val, index) { buffer[index] val; }); uni.writeBLECharacteristicValue({ deviceId: this.deviceId, serviceId: this.serviceId, characteristicId: this.characteristicId, value: buffer.buffer, success: () console.log(写入成功), fail: (err) { console.error(写入失败, err.errMsg); // 常见失败原因特征值不支持写入、value 长度超过 20 字节 } }); } function enableNotify() { uni.notifyBLECharacteristicValueChange({ deviceId: this.deviceId, serviceId: this.serviceId, characteristicId: this.characteristicId, state: true, success: () { uni.onBLECharacteristicValueChange((res) { const bytes new Uint8Array(res.value); console.log(收到设备数据, bytes); }); }, fail: (err) console.error(开启通知失败, err) }); }这段代码中的value参数类型是 ArrayBuffer不是普通的 JavaScript 数组这是新手最容易犯的错。在 H5 端你可能用stringToArrayBuffer工具转换在 App 端则直接用new Uint8Array包裹。写入长度也有限制低功耗蓝牙规范单次写入最大 20 字节部分国产蓝牙芯片实际限制在 18 字节如果写入内容超长要自己分包。实际开发时分包策略里有一个常用套路是第一个字节作为包序号第二个字节做总包数后续字节才是实际数据接收端按序号重组这个思路在做 OTA 升级时同样适用。扫码枪、热敏打印机、体脂秤这类设备数据协议本质都是「一包命令 分包应答 粘包处理」在 demo 阶段就把协议层独立成 service后期换设备只需改协议解析器不用动主流程代码这是判断模块拆分是否合理的核心标准。4. 蓝牙调试与机型适配8 个高频坑逐个拆4.1 Android 扫描不到设备先怀疑定位权限而不是蓝牙权限搜到设备但列表为空、或者搜到的全是unknown设备名是 uniapp 蓝牙开发里出现频率最高的反馈。这类问题的第一嫌疑是定位权限不是蓝牙权限。Android 的蓝牙扫描从系统 6.0 起就归入了「设备附近」类别没有ACCESS_FINE_LOCATION授权就不会返回广播包。应对方式是使用uni.getSetting检查用户已授权列表发现未授权就通过uni.authorize引导授权但这在部分国产 ROM 上可能失效原因是厂商把定位权限分成了「精确定位」和「模糊定位」两种粒度。更稳妥的方案是直接把用户带到系统设置页手动授权用一个uni.openAppAuthorizeSetting唤起授权页并在onShow生命周期里重新检查状态。uni.openAppAuthorizeSetting({ success: (res) { console.log(已跳转系统授权页, res); }, fail: (err) { console.error(跳转授权页失败, err); } });这段代码通常放在检测到「蓝牙已开启、屏幕已亮、但扫描无结果」的兜底分支里比弹窗引导更直接。另一个容易被忽略的是「一键清理加速」功能对蓝牙扫描的影响。很多手机在用户点击系统清理后会把后台蓝牙扫描任务给杀掉表现是扫描回调先正常后突然不响了。针对这个问题的缓解手段是定时器重扫比如每 15 秒调用一次uni.stopBluetoothDevicesDiscovery再调用uni.startBluetoothDevicesDiscovery设备列表只做增量更新这能在不引入原生代码的前提下显著提高国产机型的扫描稳定性。4.2 iOS 端扫描不到指定设备问题出在 CoreBluetooth 的缓存策略iOS 的蓝牙机制与 Android 最大的不同系统会对连接过的设备做缓存即使设备已断电uni.getBluetoothDevices仍可能返回旧设备记录。如果旧记录的广播包内容缺失应用端显示的名称会变成空。处理方式是对返回的设备列表做一次「新鲜度校验」比较上次扫描到的 RSSI 时间戳超过 3 秒未更新就标记为已失联从列表移除或置灰。这也解释了为什么同一个 demoAndroid 上设备总是秒出iOS 上却偶尔出现「列表里有一堆问号设备名」——那些是系统缓存中的历史设备不是扫描中新发现的设备。需要注意区分uni.getBluetoothDevices所有已发现的设备和uni.getConnectedBluethoothDevices当前已连接的设备前者用于列表展示后者用于检测耳机、手表等是否已经连接两者不要混淆使用。iOS 还会主动断开长时间无数据通信的 BLE 连接时间是大约 30 秒所以如果想要保持一个链路可以做一个 12 秒一次的心跳空包写入让系统认为链路仍是活跃状态这个心跳包在协议层通常定义为一个特定命令字接收方收到后不回包避免双方互发心跳造成消息风暴。4.3 RSSI 测距的波动和滤波demo 里聊胜于无把 RSSI 换算成距离是很多蓝牙 demo 的加分项但直接拿device.RSSI做三层地图定位会非常不准确因为 RSSI 受人的遮挡、金属表面、环境多径干扰影响剧烈。如果真要做一个粗略的接近检测可以采用滑动窗口平均的方式维护一个长度为 5 的 RSSI 数组每次取平均值再用信号损耗公式计算出分米级别的相对距离。function filterRssi(rssi) { const windowSize 5; this.rssiBuffer this.rssiBuffer || []; this.rssiBuffer.push(rssi); if (this.rssiBuffer.length windowSize) { this.rssiBuffer.shift(); } const sum this.rssiBuffer.reduce((acc, val) acc val, 0); const avgRssi sum / this.rssiBuffer.length; // 环境衰减因子 n 典型值 2-4根据实测环境修正 const n 3.0; // 距离 10 ^ ((absRSSI - A) / (10 * n))A 为 1 米处信号强度 const A -59; const distance Math.pow(10, (Math.abs(avgRssi) - A) / (10 * n)); return distance.toFixed(2); }这个滤波函数有一个与实际场景对应的参数调整逻辑空旷户外 n 取值 2.0室内办公室 n 取值 2.5 到 3.0有大量金属货架的环境直接取 4.0。A值是一米距离的标准 RSSI每种蓝牙模块的实测值都不同这个数值必须在拿到实际模块后把手机放在距离模块 1 米处记录 50 个采样点求均值不能套用别家的参数。经过这样处理后得到的结果只能用来判断「靠近 / 远离」这种二值逻辑不能用来做精确到厘米的定位。如果确实需要 1 米以内的精确定位建议改用 UWB 方案这不是 uniapp 能够直接实现的领域需要走原生插件。4.4 uniapp 蓝牙 API 在 vue2 和 vue3 中的差异升级前必须确认项目模板如果是 vue2 创建的老项目升级到 vue3 后蓝牙相关代码通常会遇到两个变化一是uni对象需要在 setup 中重新注册二是页面 onLoad 中调用的 API 触发的回调与 vue3 的 setup 生命周期存在先后顺序风险。最直接的建议是守住「蓝牙初始化必须在页面 onShow 而不是 onLoad 中触发」这一原则原因是 vue3 的 setup 中无法确认页面是否真正完成渲染而蓝牙权限弹窗需要基于已渲染的 UI 才能正确弹出。如果项目必须保留 vue2则无需迁移但要注意 vue2 的 uniapp 项目在 HBuilderX 3.6 之后进入了维护模式蓝牙相关 API 的 bug 修复会放缓有持续迭代计划的项目建议尽早迁移到 vue3 模板。4.5 多设备同时连接时的并发管理超出 demo 认知的一个盲区demo 往往只处理单个设备真实产品却常有耳机 手表 手机三端同时连接的需求。uniapp 的 API 在底层没有锁机制多个设备同时连接时特征值写入要在业务层做串行队列。用一个模块级变量保存当前写入状态同一个设备同一时刻只允许一个写操作在途其它操作排队等待。let isWriting false; const writeQueue []; function enqueueWrite(deviceId, serviceId, characteristicId, data) { return new Promise((resolve, reject) { writeQueue.push({ deviceId, serviceId, characteristicId, data, resolve, reject }); processNextWrite(); }); } function processNextWrite() { if (isWriting || writeQueue.length 0) return; const next writeQueue.shift(); isWriting true; uni.writeBLECharacteristicValue({ deviceId: next.deviceId, serviceId: next.serviceId, characteristicId: next.characteristicId, value: next.data, success: () { isWriting false; next.resolve(); processNextWrite(); }, fail: (err) { isWriting false; next.reject(err); processNextWrite(); } }); }这个串行队列在处理 OTA 升级场景时格外有用因为 OTA 往往需要连续写入几十个包而蓝牙芯片的接收缓冲区只有 20 字节写入速度过快会导致丢包。在实际压测中队列里每包直接写入间隔建议保持在 20-40 毫秒之间低于 10 毫秒时多数芯片会出现明显丢包。队列设计时还要加一个“同设备任务只在队列里保留一个”的优化如果连续多次写入同一个特征值只保留最后一次的写入内容避免控制类指令一个接一个排队造成的卡顿感。4.6 蓝牙状态监听不能重复注册分包加载与代码优化的关系很多人在页面 onHide 里忘记uni.offBLEConnectionStateChange结果从 A 页面跳到 B 页面再返回同一个回调被注册了两遍一旦断开事件触发逻辑会执行两次。规范做法是在 onLoad 里注册监听在 onUnload 里注销用页面引用计数控制监听器的唯一性。这里涉及一个分包策略如果蓝牙模块代码是独立分包的内容页面销毁时可能直接卸载整包此时再注销监听反而会报错更安全的方式是使用全局监听器统一管理。在一个定时器驱动的全局事件中心里注册唯一的设备断开处理器页面只需订阅该处理器返回的状态即可这样即使分包被加载和卸载循环多次监听器也不会成倍增长。音频类设备还要留意后台保活的问题uniapp 的 js 层在应用进入后台后会被系统冻结此时收到的蓝牙通知无法实时处理这类场景必须要走原生插件才能保证数据完整。5. 真机连调失败时按这套方法逐层定位拿到 demo 工程后第一次真机运行一般不会顺。把失败拆成下面几层按顺序排查能省掉大多数抓狂重组的时间。故障表现直接原因定位手段openBluetoothAdapter 报 10001系统蓝牙未开启系统设置中手动开启后重新编译扫描无任何返回值未授权定位权限检查 manifest 中权限并去系统设置手动授权扫描有设备连接必失败设备地址为随机地址已失联让设备重新进入广播状态后重试连接成功但无服务列表设备是 Bluetooth Classic 而非 BLE查阅硬件手册确认协议类型写入成功但设备无响应特征值不具备写入属性用 nRF Connect 或调试工具确认真实属性通知回调不触发notify 未使能或 UUID 写错检查 notifyBLECharacteristicValueChange 是否调用成功还有个重要排错手段在校园或办公园区有多个同型号蓝牙网关的设备环境里扫描列表里会出现多个同名设备用deviceId来连接而不是用设备名这是最容易忽视的「bug 免责声明」。如果在真机调试中遇到10008错设备连接中无法操作建议先closeBLEConnection清理状态再重试比手动重启蓝牙要稳定得多。6. 从 demo 到上架打包、签名与应用市场审核的必经步骤6.1 Android 云打包与本地打包的选型基座版本差异不能忽视uniapp 项目开发期用自定义调试基座运行但做正式包时有两种选择云打包和本地打包。云打包是 HBuilderX 提供的在线服务把代码和 manifest 上传打包优点是简单缺点是排错难原生插件冲突时只能反复尝试。本地打包需要下载 Android Studio 工程生成代码适合有原生开发经验、需要嵌入自研原生插件或接特殊签名方案的团队。无论哪种方式正式包必须使用正式证书自定义调试基座的证书无法上架应用市场这个是硬性要求。# Android 密钥生成参考命令仅离线打包时需要 keytool -genkey -alias app-bluetooth-demo -keyalg RSA -validity 36500 -keystore app-bluetooth-demo.keystore这里的keytool是 JDK 自带工具-validity 36500表示证书有效期为 100 年应用市场通常要求证书有效期覆盖应用生命周期建议一次性设为这个值。签名时要记录好密钥库口令和别名Google Play 后续的上传密钥更新机制需要用到这些信息。另外 Android 的targetSdkVersion直接影响蓝牙权限的申请规则target 31 以上才需要BLUETOOTH_SCAN和BLUETOOTH_CONNECT运行时权限target 30 以下系统会自动降级为老权限模型不建议为了绕过权限适配刻意压低 target 版本应用市场近期对 target 版本的要求一直在上调。6.2 iOS 打包证书与描述文件App Store 审核对蓝牙权限的描述要求iOS 打包必须走 Apple 开发者账号需要先在 Apple Developer 后台创建 App ID开启 Bluetooth 能力并生成对应的描述文件。这块常见的坑是 Bundle ID 与描述文件不匹配导致安装失败且报错信息并不直观。HBuilderX 中云打包 iOS 需要上传 p12 证书和 .mobileprovision 文件打包成功后用 TestFlight 或蒲公英进行内部分发测试。App Store 审核时对蓝牙权限描述文案有一定要求必须在用途描述中清楚说明蓝牙数据的使用场景比如「连接设备同步健康数据」这种具体描述模板化的措辞可能会被打回。构建版本里如果有 IDFA广告标识符相关代码还必须声明用途但蓝牙 demo 通常不涉及在构建设置里确认NSUserTrackingUsageDescription是否被某些统计 SDK 引入即可。6.3 热更新与蓝牙 SDK 的兼容性离线打包资源包的降级策略uniapp 的正式包支持热更新即通过 wgt 资源包直接替换前端代码。但蓝牙模块的处理逻辑有中断风险如果热更新过程中用户正在使用蓝牙传输数据页面切换会造成 BLE 连接的正常断开这是用户可感知的。实现思路是在热更新下载完成后先将新资源包缓存到本地在蓝牙空闲期判断特征值写入队列为空并且无连接再执行替换并在替换前调用uni.closeBLEConnection主动断开所有连接最后提示用户重新打开应用。如果热更新包中涉及新增原生插件或原生权限升级无法生效必须走整包升级。这是 uniapp 工程区分「资源热更」和「原生能力变更」两种升级路径的核心约束也是从 demo 走向正式产品时最容易被误解的一环。6.4 蓝牙测距在室内定位场景中的工程化改造方向如果你的蓝牙 demo 最终要走向蓝牙测距或室内定位应用那核心方向不是写更多 uniapp 代码而是建一个由固定位置蓝牙信标组成的基站地图。具体做法是在数据库中保存每个信标的物理坐标、UUID、Major、Minor 值手机扫描到信标后用 RSSI 结合三点定位或多边测量推算相对位置这类实现的精度在 2 到 5 米之间已经可以用于场馆导览这类不需要精确到桌面的场景。uniapp 在这一环节中扮演的角色只是数据采集和可视化真正的定位计算通常部署在服务端或本地 worker 中因为大量信标同时上报时前端页面做坐标解算会造成 UI 卡顿。数据上报则建议采用 mqtt 协议的长连接每 3 秒聚合一次扫描结果批量发送避免高频小包导致服务端压力陡增。最终在上架前拿至少 3 台不同厂家 Android 机型做 1 小时以上的压力跑测。本文还有配套的精品资源点击获取
返回列表