更新记录
1.0.0(2026-10-31)
初始版本
- 安卓经典蓝牙(经典蓝牙 / SPP)操作完整能力,统一为 zy-using-bluetooth UTS 插件
- 权限:申请插件所需运行时权限、检查「所有文件」访问权限、跳转「所有文件」访问权限页
- 基础能力:初始化(注册事件监听)、释放资源
- 蓝牙开关:检查是否支持蓝牙、检查是否已开启、开启蓝牙、关闭蓝牙、跳转蓝牙设置界面
- 搜索与配对:获取已配对设备、搜索设备、停止搜索、设备配对、取消配对
- 连接:连接设备、断开连接、检查是否已连接、获取当前连接的设备
- 发送数据:发送字节数据、发送十六进制字符串、发送文本数据、发送文件、选择文件并发送
- 文件接收:收到传输请求后确认或拒绝接收,接收进度与完成结果持续回调
- 事件:开关状态、发现设备、搜索开始/结束、配对过程、连接状态、数据收发、文件传输进度、内部错误
平台兼容性
uni-app(4.72)
| Vue2 |
Vue2插件版本 |
Vue3 |
Vue3插件版本 |
Chrome |
Safari |
app-vue |
app-vue插件版本 |
app-nvue |
app-nvue插件版本 |
Android |
Android插件版本 |
iOS |
鸿蒙 |
| √ |
1.0.0 |
√ |
1.0.0 |
× |
× |
√ |
1.0.0 |
√ |
1.0.0 |
5.0 |
1.0.0 |
× |
× |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| × |
× |
× |
× |
× |
× |
× |
× |
× |
× |
× |
× |
uni-app x(4.72)
| Chrome |
Safari |
Android |
Android插件版本 |
iOS |
鸿蒙 |
微信小程序 |
| × |
× |
5.0 |
1.0.0 |
× |
× |
× |
插件介绍
安卓经典蓝牙(Bluetooth Classic / SPP)操作 UTS 原生插件。一个插件覆盖蓝牙开关、设备搜索与配对、SPP 连接、数据收发(字节 / 十六进制 / 文本 / 文件)与文件接收确认,并提供运行时权限申请与「所有文件」访问权限检查。
支持 uni-app 与 uni-app x 不依赖任何三方库。
使用场景
- 连接蓝牙打印机、蓝牙电子秤、蓝牙读卡器等 SPP 串口类外设,收发指令与数据
- 与另一台手机 / 工位机做点对点文件互传(发送方发起请求,接收方确认后接收)
- 需要先引导用户开启蓝牙、授权蓝牙扫描权限的设备联调场景
- 需要把接收到的文件落到本机目录、再交给业务处理的场景
使用流程
- 申请权限:
requestPermission() 申请蓝牙扫描 / 连接(Android 12+)或定位(Android 6~11)权限;接收文件到公共目录还需 checkAllFilesPermission() + toAllFilesPermissionPage() 引导授权。
- 初始化:
init({ onEvent }) 注册事件回调,之后所有状态变化都从 onEvent 回调(type 区分事件)。
- 检查并开启蓝牙:
isBluetoothSupported() → isBluetoothEnabled() → enableBluetooth()(弹系统授权框)。
- 搜索与配对:
getPairedDevices() 或 startDiscovery()(设备从 onDeviceFound 事件取),stopDiscovery() 停止;必要时 pairDevice({ address }) 配对。两台手机互连时,接收方先 enableDiscoverable() 才能被对方搜到。
- 连接:
connect({ address }),连接结果以 onConnectionStateChanged 事件的 state == 3 为准。
- 收发数据:
sendText / sendHexString / sendBytes / sendFile / selectFileAndSend;收到的数据从 onDataReceived 事件取;对端发文件时在 onFileReceiveRequest 里调用 confirmFileReceive({ accept }) 决定收不收。
- 释放:
disconnect() 断开,release() 释放全部资源(建议放在页面 onUnload)。
用法
import {
requestPermission,
checkAllFilesPermission,
toAllFilesPermissionPage,
init,
release,
isBluetoothSupported,
isBluetoothEnabled,
enableBluetooth,
disableBluetooth,
openBluetoothSettings,
getPairedDevices,
startDiscovery,
stopDiscovery,
pairDevice,
unpairDevice,
connect,
disconnect,
isConnected,
getConnectedDevice,
sendBytes,
sendHexString,
sendText,
sendFile,
selectFileAndSend,
confirmFileReceive
} from '@/uni_modules/zy-using-bluetooth'
权限申请
requestPermission({
success: (res) => {
// res.list:[{ name: 'android.permission.BLUETOOTH_CONNECT', granted: true }]
// res.allGranted:是否全部授予
// res.permanentlyDenied:被永久拒绝的权限名(需引导去设置页手动开启)
console.log('全部授予:' + res.allGranted)
},
fail: (err) => console.log('失败 ' + err.errCode + ' ' + err.errMsg)
})
// 指定权限列表(缺省为插件所需的全部运行时权限)
requestPermission({ permissions: ['android.permission.BLUETOOTH_SCAN'] })
所有文件访问权限
checkAllFilesPermission({
success: (res) => {
// res.result:true 已授权 / false 未授权
console.log('所有文件权限:' + res.result)
}
})
// Android 11 及以上跳系统「所有文件访问权限」页;以下跳应用详情页
toAllFilesPermissionPage({
success: () => console.log('已跳转,返回后可重新检查')
})
初始化与释放
init({
onEvent: (event) => {
// event.type 取值见「事件结构」;所有事件都在主线程回调
console.log('事件:' + event.type)
if (event.type === 'onBluetoothStateChanged') {
console.log('蓝牙是否开启:' + event.isEnabled)
} else if (event.type === 'onDeviceFound') {
console.log(event.deviceName + ' ' + event.deviceAddress + ' RSSI ' + event.deviceRssi)
} else if (event.type === 'onConnectionStateChanged') {
// state:0 未连接 | 1 监听中 | 2 连接中 | 3 已连接
console.log('连接状态:' + event.state)
} else if (event.type === 'onDataReceived') {
// data:按 UTF-8 解码的文本;hex:大写十六进制;length:字节数
console.log('收到 ' + event.length + ' 字节:' + event.hex)
} else if (event.type === 'onFileReceiveRequest') {
// 收到对端的文件传输请求,由业务决定是否接收
confirmFileReceive({ accept: true })
}
},
success: () => console.log('初始化成功'),
fail: (err) => console.log('初始化失败 ' + err.errCode + ' ' + err.errMsg)
})
蓝牙开关
isBluetoothSupported({ success: (res) => console.log('支持蓝牙:' + res.result) }) // 不需要 init
isBluetoothEnabled({ success: (res) => console.log('蓝牙已开启:' + res.result) }) // 不需要 init
enableBluetooth({ success: () => console.log('已弹系统开启框') }) // 结果以 onBluetoothStateChanged 为准
disableBluetooth({ success: (res) => console.log('关闭结果:' + res.result) })
openBluetoothSettings({ success: () => console.log('已跳转蓝牙设置') })
搜索与配对
getPairedDevices({
success: (res) => {
// res.list:[{ name, address, bondState }],bondState:10 未配对 | 11 配对中 | 12 已配对
console.log('已配对 ' + res.list.length + ' 个')
}
})
startDiscovery({ success: () => console.log('开始搜索') })
stopDiscovery({ success: () => console.log('停止搜索') })
pairDevice({
address: '00:11:22:33:44:55',
success: () => console.log('配对请求已发起'),
fail: (err) => console.log('配对失败 ' + err.errCode + ' ' + err.errMsg)
})
unpairDevice({ address: '00:11:22:33:44:55' })
连接
connect({
address: '00:11:22:33:44:55',
success: () => console.log('连接请求已发起,等待 onConnectionStateChanged'),
fail: (err) => console.log('连接失败 ' + err.errCode + ' ' + err.errMsg)
})
isConnected({ success: (res) => console.log('已连接:' + res.result) })
getConnectedDevice({ success: (res) => console.log(res.device.name + ' ' + res.device.address) })
disconnect()
发送数据
// 字节数组,元素取值 0~255
sendBytes({ data: [0x01, 0x02, 0x0A] })
// 也可以直接传十六进制 / base64 字符串(大包推荐,省掉 number[] 的 JSON 序列化开销)
sendBytes({ hex: '01 02 0A' })
sendBytes({ base64: 'AQIK' })
// 十六进制字符串(空格会被忽略,长度必须为偶数)
sendHexString({ data: '01 02 0A' })
// 文本,按 UTF-8 编码
sendText({ data: 'hello bluetooth' })
// 发送文件:先发传输请求,对端确认后开始发送(30 秒未确认则失败)
sendFile({ path: '/storage/emulated/0/test.txt' })
// 打开系统文件选择器,选中后自动发送(文件会先复制到应用私有目录)
selectFileAndSend({
success: (res) => console.log('开始发送 ' + res.fileName + '(' + res.fileSize + ' 字节)')
})
接收文件
// 收到 onFileReceiveRequest 后决定
confirmFileReceive({
accept: true, // false 为拒绝,已接收的部分会被删除
saveDir: '/storage/emulated/0/Download', // 可选,缺省为应用私有 Download 目录
success: () => console.log('已回复对端')
})
接收目录不存在会自动创建;不可写时回退到应用私有目录并回调 onError(9250014)。
同名文件已存在时自动追加 (1)(2)…,不会覆盖已有文件。
本机可被发现(两台手机互连)
// 让对方能搜到本机并发起配对,duration 缺省 120 秒
enableDiscoverable({
duration: 120,
success: () => console.log('已弹系统「允许被搜索」框')
})
// 结果看事件
// onScanModeChanged -> { scanMode: 23, discoverable: true }
插件方法
| 方法 |
说明 |
回调 |
requestPermission(options?) |
申请插件所需运行时权限,options.permissions 可覆盖缺省列表 |
成功 list / allGranted / permanentlyDenied |
checkAllFilesPermission(options?) |
检查「所有文件」访问权限 |
成功 result(布尔) |
toAllFilesPermissionPage(options?) |
跳转「所有文件」访问权限页 |
成功 errMsg |
init(options?) |
初始化并注册事件回调,必须先调用 |
成功 errMsg;事件走 onEvent |
release(options?) |
断开连接、反注册广播、清空状态 |
成功 errMsg |
isBluetoothSupported(options?) |
检查设备是否支持蓝牙(可不 init) |
成功 result(布尔) |
isBluetoothEnabled(options?) |
检查蓝牙是否已启用(可不 init) |
成功 result(布尔) |
enableBluetooth(options?) |
弹系统授权框开启蓝牙 |
成功 errMsg;结果以 onBluetoothStateChanged 为准 |
disableBluetooth(options?) |
关闭蓝牙(已开启时才能调用) |
成功 result(布尔) |
openBluetoothSettings(options?) |
跳转系统蓝牙设置页 |
成功 errMsg |
enableDiscoverable(options?) |
让本机可被其他设备搜索到,options.duration 为时长(秒,缺省 120) |
成功 errMsg;结果以 onScanModeChanged 为准 |
getPairedDevices(options?) |
获取已配对设备 |
成功 list |
startDiscovery(options?) |
搜索设备(连接中 / 已连接时不可用) |
成功 errMsg;设备走 onDeviceFound |
stopDiscovery(options?) |
停止搜索 |
成功 errMsg |
pairDevice(options) |
设备配对,options.address 必传(已搜索/已配对的设备直接用实例,否则按 MAC 构造) |
成功 errMsg;过程走 onPairingRequest / onPairingCompleted |
unpairDevice(options) |
取消配对,options.address 必传 |
成功 errMsg |
connect(options) |
连接设备(SPP,UUID 00001101-0000-1000-8000-00805F9B34FB),options.address 必传 |
成功 errMsg;结果走 onConnectionStateChanged |
disconnect(options?) |
断开连接 |
成功 errMsg |
isConnected(options?) |
检查是否已连接 |
成功 result(布尔) |
getConnectedDevice(options?) |
获取当前连接的设备(未连接返回 9250006) |
成功 device |
sendBytes(options) |
发送字节数据,data(0~255 数组)/ hex / base64 三选一(优先级 hex > base64 > data) |
成功 errMsg |
sendHexString(options) |
发送十六进制字符串,options.data |
成功 errMsg |
sendText(options) |
发送文本(UTF-8),options.data |
成功 errMsg |
sendFile(options) |
发送文件,options.path 为绝对路径 |
成功 errMsg;进度走 onFileSendProgress / onFileSendComplete |
selectFileAndSend(options?) |
打开系统文件选择器并发送 |
成功 fileName / filePath / fileSize |
confirmFileReceive(options) |
确认或拒绝接收文件,options.accept、options.saveDir(可选,接收目录) |
成功 errMsg |
回调结果结构
// 通用失败对象(UniError)
{
errSubject : 'zy-using-bluetooth',
errCode : 9250006, // 见「错误码」
errMsg : '蓝牙未连接' // 错误描述
}
// 设备对象(getPairedDevices 的 list 元素、getConnectedDevice 的 device)
{
name : 'BT-Printer', // 设备名称,未取到为空字符串
address : '00:11:22:33:44:55', // MAC 地址
bondState : 12 // 10 未配对 | 11 配对中 | 12 已配对
}
// selectFileAndSend 成功结果
{
fileName : 'test.txt', // 文件名
filePath : '/data/user/0/<包名>/cache/test.txt', // 复制到私有目录后的路径
fileSize : 1024, // 字节数
errMsg : '文件已选择,开始发送'
}
事件结构(init 的 onEvent)
{
type : 'onDeviceFound', // 事件类型,见下表
deviceName : 'BT-Printer', // 设备名称(设备相关事件)
deviceAddress : '00:11:22:33:44:55',
deviceBondState : 12, // 10 未配对 | 11 配对中 | 12 已配对
deviceRssi : -63, // 信号强度(dBm,通常为负值),onDeviceFound 时有效
isEnabled : true, // 蓝牙是否开启,onBluetoothStateChanged / onInit 时有效
state : 3, // 0 未连接 | 1 监听中 | 2 连接中 | 3 已连接,onConnectionStateChanged 时有效
success : true, // 配对/发送是否成功,onPairingCompleted / onFileSendComplete / onFileReceiveComplete 时有效
data : 'hello', // 数据文本(UTF-8 解码),onDataReceived / onDataSent 时有效
hex : '68656C6C6F', // 数据十六进制(大写、无分隔符)
length : 5, // 数据字节数
fileName : 'test.txt', // 文件名(文件相关事件)
totalSize : 1024, // 文件总大小(字节,发送/接收进度事件)
sentSize : 512, // 已发送字节数,onFileSendProgress 时有效
receivedSize : 512, // 已接收字节数,onFileReceiveProgress 时有效
fileSize : 1024, // 待传输文件大小,onFileReceiveRequest 时有效
filePath : '/storage/emulated/0/Android/data/<包名>/files/Download/test.txt', // 接收完成的文件路径
confirmed : true, // 对端是否同意接收,onFileReceiveConfirm 时有效
errCode : 0, // 错误码(9250xxx),onError 时有效
errorMessage : '', // 错误描述,onError 时有效
scanMode : 23, // 20 不可被发现 | 21 可被连接 | 23 可被连接且可被发现,onScanModeChanged 时有效
discoverable : true // 本机是否可被搜索到,onScanModeChanged 时有效
}
事件类型:
type |
触发时机 |
关键字段 |
onInit |
初始化完成 |
isEnabled |
onBluetoothStateChanged |
蓝牙开关状态变化 |
isEnabled |
onDeviceFound |
搜索到一个设备 |
deviceName / deviceAddress / deviceBondState / deviceRssi |
onDiscoveryStarted |
开始搜索 |
- |
onDiscoveryStopped |
搜索结束(搜索超时或被停止) |
- |
onPairingRequest |
开始配对 |
deviceName / deviceAddress |
onPairingCompleted |
配对结束 |
success / deviceAddress |
onConnectionStateChanged |
连接状态变化 |
state / deviceAddress |
onDataReceived |
收到对端数据 |
data / hex / length |
onDataSent |
本端数据发送成功 |
data / hex / length |
onFileSendProgress |
文件发送进度(约每 8KB 一次,间隔 ≥ 5 毫秒) |
fileName / sentSize / totalSize |
onFileSendComplete |
文件发送结束 |
fileName / success |
onFileReceiveRequest |
收到对端文件传输请求 |
fileName / fileSize / deviceName |
onFileReceiveProgress |
文件接收进度(每 100 毫秒最多一次) |
fileName / receivedSize / totalSize |
onFileReceiveComplete |
文件接收结束 |
fileName / filePath / success |
onFileReceiveConfirm |
对端回复确认/拒绝 |
fileName / confirmed |
onScanModeChanged |
本机可被发现状态变化 |
scanMode / discoverable |
onError |
内部错误(连接断开、写入失败、文件传输失败等) |
errCode / errorMessage |
错误码
| 错误码 |
说明 |
| 9250001 |
参数错误 |
| 9250002 |
未初始化,请先调用 init |
| 9250003 |
设备不支持蓝牙 |
| 9250004 |
蓝牙未开启 |
| 9250005 |
蓝牙已开启 |
| 9250006 |
蓝牙未连接 |
| 9250007 |
文件不存在 |
| 9250008 |
缺少蓝牙扫描权限,请先调用 requestPermission 申请 |
| 9250009 |
缺少蓝牙连接权限,请先调用 requestPermission 申请 |
| 9250010 |
缺少位置权限,搜索设备需要位置权限 |
| 9250011 |
连接失败 |
| 9250012 |
未找到蓝牙设备,请先搜索或获取已配对设备 |
| 9250013 |
获取上下文失败 |
| 9250014 |
文件传输失败 |
| 9250015 |
操作失败 |
权限说明
| 权限 |
用途 |
android.permission.BLUETOOTH |
Android 11 及以下:蓝牙基础操作(开启、搜索、连接) |
android.permission.BLUETOOTH_ADMIN |
Android 11 及以下:开启 / 关闭蓝牙、发起搜索与配对 |
android.permission.BLUETOOTH_CONNECT |
Android 12 及以上:查询已配对设备、连接、配对、收发数据(运行时权限) |
android.permission.BLUETOOTH_SCAN |
Android 12 及以上:搜索附近蓝牙设备(运行时权限) |
android.permission.ACCESS_FINE_LOCATION |
Android 11 及以下:搜索蓝牙设备必需的定位权限(运行时权限) |
android.permission.ACCESS_COARSE_LOCATION |
Android 11 及以下:辅助定位权限,与精确定位一起申请 |
android.permission.READ_EXTERNAL_STORAGE |
Android 10 及以下:读取待发送文件 |
android.permission.WRITE_EXTERNAL_STORAGE |
Android 10 及以下:写入接收到的文件 |
android.permission.MANAGE_EXTERNAL_STORAGE |
Android 11 及以上:「所有文件」访问权限(特殊权限,需跳转设置页由用户手动授予) |