Uniapp集成native.js实现安卓经典蓝牙串口通信的实战与避坑指南

Uniapp集成native.js实现安卓经典蓝牙串口通信的实战与避坑指南 1. 为什么需要native.js实现经典蓝牙通信最近在做一个智能硬件的控制项目客户要求用Uniapp开发安卓应用通过蓝牙与他们的设备通信。刚开始我信心满满想着Uniapp官方不是有蓝牙API吗结果一上手就傻眼了 - 官方API只支持BLE低功耗蓝牙而客户的设备用的是经典蓝牙协议SPP串口协议。这就好比你想用USB接口结果发现设备只有老式的串口。这种情况在工业控制、打印机、POS机等场景特别常见。很多老设备还在使用经典蓝牙协议而Uniapp官方文档里压根没提这茬。我翻遍了DCloud论坛发现不少开发者都卡在这个问题上。有人建议用原生开发但这意味着要放弃跨平台优势也有人尝试用WebSocket中转但这增加了系统复杂度。后来我发现native.js这个黑科技 - 它能让Uniapp直接调用安卓原生API。简单来说就像给JavaScript开了个后门让它能直接跟Java对话。不过要注意这个方法目前只支持安卓平台iOS还是得另想办法。2. 环境准备与基础配置2.1 项目初始化首先用HBuilderX新建一个Uniapp项目选择默认模板就行。重点是要在manifest.json里做好配置{ app-plus: { android: { permissions: [ android.permission.BLUETOOTH, android.permission.BLUETOOTH_ADMIN, android.permission.ACCESS_COARSE_LOCATION, android.permission.ACCESS_FINE_LOCATION ] } } }这几个权限缺一不可特别是位置权限 - 安卓6.0以后蓝牙扫描必须要有位置权限这个坑我踩过好几次。建议在页面加载时就动态申请权限uni.authorize({ scope: scope.bluetooth, success() { console.log(蓝牙授权成功) } })2.2 引入关键库文件经过多次测试我发现gitee上的BluetoothTool.js确实好用但直接拿来用会遇到不少问题。建议下载后做以下修改在项目根目录新建nativejs文件夹放入修改后的BluetoothTool.js在main.js中全局引入import BluetoothTool from /nativejs/BluetoothTool.js Vue.prototype.$bluetooth BluetoothTool这个库的核心是封装了安卓的BluetoothAdapter和BluetoothSocket通过native.js桥接让Uniapp能调用这些原生接口。我对比过几个开源方案这个库的稳定性最好。3. 核心功能实现详解3.1 设备搜索与连接搜索设备时要特别注意时序控制。很多开发者反映搜不到设备问题往往出在这里this.$bluetooth.discoveryNewDevice((devices) { this.deviceList devices uni.stopBluetoothDevicesDiscovery() // 及时停止搜索 }, (err) { console.error(搜索出错:, err) })连接设备时有个关键点 - 必须指定UUID。经典蓝牙通常使用SPP协议的通用UUIDconst SPP_UUID 00001101-0000-1000-8000-00805F9B34FB this.$bluetooth.createBond(device.address, SPP_UUID, (res) { console.log(连接成功, res) }, (err) { console.error(连接失败, err) })3.2 数据收发处理数据通信要处理好粘包和编码问题。建议采用以下方案// 发送数据 this.$bluetooth.sendData(str2bytes(Hello World), (res) { console.log(发送成功, res) }) // 接收数据 this.$bluetooth.onReceivedData((data) { const msg bytes2str(data) // 字节转字符串 console.log(收到数据:, msg) }) // 工具函数 function str2bytes(str) { return new java.lang.String(str).getBytes(GBK) } function bytes2str(bytes) { return new java.lang.String(bytes, GBK) }工业设备通信常用GBK编码如果用UTF-8可能会出现乱码。另外建议加个简单的数据校验比如CRC16。4. 打包上线的那些坑4.1 搜索不到设备的玄学问题最坑的就是开发时一切正常打包成APK后突然搜不到设备。这个问题困扰了我三天最后发现是少了这个关键调用uni.startBluetoothDevicesDiscovery({ success(res) { console.log(蓝牙搜索初始化成功) this.$bluetooth.discoveryNewDevice() // 必须在这之后调用 } })原理是native.js需要先激活Uniapp的蓝牙模块。建议把这个逻辑封装成初始化方法在App启动时就执行。4.2 权限动态申请即使manifest.json配置了权限安卓6.0仍然需要动态申请。我推荐这样处理const checkPermission () { uni.getSetting({ success(res) { if (!res.authSetting[scope.bluetooth]) { uni.authorize({ scope: scope.bluetooth, fail() { uni.showModal({ content: 请授权蓝牙权限, confirmText: 去设置, success(res) { if (res.confirm) { uni.openSetting() } } }) } }) } } }) }4.3 兼容性问题测试中发现不同安卓版本表现差异很大安卓版本主要问题解决方案5.x权限申请方式不同使用旧版API8.0后台扫描限制增加前台服务10定位权限要求更严格动态申请精确定位建议在真机上多做测试特别是华为、小米等国产机型它们的系统定制可能会带来额外问题。5. 性能优化与稳定性提升5.1 连接保活机制蓝牙连接很容易意外断开我设计了这个重连方案let reconnectCount 0 const maxReconnect 3 this.$bluetooth.onDisconnected(() { if (reconnectCount maxReconnect) { setTimeout(() { this.connectDevice(device) reconnectCount }, 2000) } })同时建议加个心跳包机制每隔30秒发送一个空包检测连接状态。5.2 数据缓冲区处理当数据量较大时建议实现个简单的缓冲队列class DataBuffer { constructor() { this.queue [] this.isSending false } add(data) { this.queue.push(data) this.checkSend() } checkSend() { if (!this.isSending this.queue.length) { this.isSending true const data this.queue.shift() this.$bluetooth.sendData(data, () { this.isSending false this.checkSend() }) } } }这个方案在我对接POS打印机时特别有效避免了数据堵塞问题。6. 实际项目中的经验分享最近用这套方案完成了两个工业项目总结几个实用技巧设备配对问题有些设备需要先通过系统设置配对才能在App中连接。可以在代码里加个跳转系统蓝牙设置的按钮。超时设置所有蓝牙操作都要加超时控制我一般设置15秒const timeout setTimeout(() { reject(new Error(操作超时)) }, 15000) this.$bluetooth.someMethod(() { clearTimeout(timeout) // ... })日志记录建议把关键操作和通信数据记录到本地文件方便排查问题const log (msg) { const date new Date().toLocaleString() plus.io.writeFile(_doc/log.txt, ${date}: ${msg}\n, {append: true}) }UI反馈优化蓝牙操作往往比较慢要做好加载状态提示。我习惯用uni.showLoading配合状态机管理。这套方案已经在多个真实项目中验证过稳定性单设备连续工作72小时无异常。最大的体会是一定要处理好各种异常情况蓝牙开发中意外才是常态。