24_微信小程序蓝牙-设备搜寻篇
注: 示例代码使用uniapp编写,下面通过uni.
调用的API侧重于微信侧,等同于wx.
,
上一篇文章: 23_微信小程序蓝牙-权限适配篇
中已经讲清楚了uni.openBluetoothAdapter
的用法,以及关于权限会出现的问题以及适配方案,只有蓝牙模块初始化成功,后续的蓝牙相关API才能调用成功,设备搜寻也不例外。
一.uni.openBluetoothAdapter
封装
uni.openBluetoothAdapter
一旦调用成功,如果有其他地方也需要调用时,必须先调用uni.closeBluetoothAdapter
,uni.openBluetoothAdapter
才能被再次调用成功
在实际业务中,如果有多个地方使用了uni.openBluetoothAdapter
,如果处理不当,也是会出现问题的。续接上一篇文章对uni.openBluetoothAdapter
进行封装,保证uni.openBluetoothAdapter
全局只会被调用一次。
/**
* 蓝牙权限待申请
*/
const BLUETOOTH_AUTHOR_PENDING:string = "BLUETOOTH_AUTHOR_PENDING"
/**
* 蓝牙权限被拒绝
*/
const BLUETOOTH_AUTHOR_REJECT:string = "BLUETOOTH_AUTHOR_REJECT"
/**
* BluetoothAdapter不可用
*/
const BLUETOOTH_ADAPTER_UNAVAILABLE:string = "BLUETOOTH_ADAPTER_UNAVAILABLE"
/**
* 蓝牙适配器可用
*/
const BLUETOOTH_ADAPTER_AVAILABLE:string = "BLUETOOTH_ADAPTER_AVAILABLE"
export interface BluetoothManager {
on(type: string, callback: (status: string) => void): void;
off(type: string, callback: (status: string) => void): void;
destroy(): void;
}
function dispatchBluetoothStatus(status: string): void {
this.statusCallbacks.forEach((func) => {
func && func(status)
})
}
function initializeBluetooth(): Promise<any> {
this.status = BLUETOOTH_AUTHOR_PENDING
dispatchBluetoothStatus.call(this, this.status)
const scope:string = "scope.bluetooth"
const openBluetoothAdapter = ():Promise<any> => {
return uni.openBluetoothAdapter().catch((err) => {
this.status = BLUETOOTH_ADAPTER_UNAVAILABLE
dispatchBluetoothStatus.call(this, this.status)
return Promise.reject()
}).then((res) => {
this.status = BLUETOOTH_ADAPTER_AVAILABLE
dispatchBluetoothStatus.call(this, this.status)
return Promise.resolve()
})
}
const openSetting = () => {
return uni.showModal({
content: "您需要在设置中开启\"蓝牙\"选项,才可以使用蓝牙哦!",
cancelText: "任性不去",
confirmText: "去设置",
}).then((res) => {
if (res.confirm) {
return new Promise((resolve, reject) => {
this.openSettingResolves.push(resolve)
uni.openSetting().catch((error) => {
reject({
"errMsg": "openSetting:fail system error",
"errno": -2
})
})
}).then((res) => {
let authSetting = res.authSetting || {}
if (authSetting[scope]) {
return Promise.resolve()
} else {
return openSetting()
}
})
} else {
return Promise.reject({
"errMsg": "authorize:fail auth deny",
"errno": 103
})
}
})
}
const getSetting = () => {
return uni.getSetting().catch((error) => {
return Promise.reject({
"errMsg": "authorize:fail system error",
"errno": -2
})
}).then((res) => {
let authSetting = res.authSetting || {}
let keys = Object.keys(authSetting)
if (keys.indexOf(scope) != -1) {
if(!authSetting[scope]) {
return openSetting()
} else {
return Promise.resolve()
}
} else {
return Promise.reject({
"errMsg": "authorize:fail auth canceled",
"errno": -3
})
}
})
}
return uni.authorize({scope: scope}).catch((err) => {
return getSetting()
}).catch((err) => {
this.status = BLUETOOTH_AUTHOR_REJECT
dispatchBluetoothStatus.call(this, this.status)
return Promise.reject()
}).then((res) => {
return uni.closeBluetoothAdapter().catch((err) => Promise.resolve())
}).then((res) => {
return openBluetoothAdapter()
})
}
class BluetoothManagerInstance implements BluetoothManager {
private status:string;
private statusCallbacks: Array<(status: string) => void> = [];
private openSettingResolves: Array<(result:any) => void> = [];
private onAppShow: (options: any) => void = (options): void => {
uni.getSetting().then((res) => {
if(this.openSettingResolves.length > 0) {
this.openSettingResolves.forEach((resolveFunc) => resolveFunc(res))
this.openSettingResolves = []
} else {
let authSetting = res.authSetting || {}
let keys = Object.keys(authSetting)
if(keys.indexOf("scope.bluetooth") != -1) {
if (!authSetting["scope.bluetooth"]) {
if(this.status != BLUETOOTH_AUTHOR_PENDING) {
this.status = BLUETOOTH_AUTHOR_REJECT
dispatchBluetoothStatus.call(this, this.status)
}
} else {
if(this.status == BLUETOOTH_AUTHOR_REJECT) {
initializeBluetooth.call(this);
}
}
}
}
return Promise.resolve()
}).catch((err) => {
return Promise.resolve()
})
}
constructor() {
uni.onAppShow(this.onAppShow);
initializeBluetooth.call(this);
}
on(type: string, callback: (result: any) => void): void {
if(type == "statusChange") {
this.statusCallbacks.push(callback)
}
}
off(type: string, callback: (result: any) => void): void {
if(type == "statusChange") {
let callbackIndex = this.statusCallbacks.findIndex((item) => item == callback)
if(callbackIndex == -1) {
return
}
this.statusCallbacks.splice(callbackIndex, 1)
}
}
destroy(): void {
this.status = BLUETOOTH_AUTHOR_PENDING
uni.closeBluetoothAdapter().catch(() => undefined)
this.statusCallbacks = []
uni.offAppShow(this.onAppShow)
}
}
export const createBluetoothManager = ():BluetoothManager => {
return new BluetoothManagerInstance();
}
使用的时候,只需要在App.vue
中,调用createBluetoothManager初始化,就能保证全局只调用一次。
<!-- App.vue -->
<script>
import { createBluetoothManager } from '@/bluetooth/bluetooth-manager'
export default {
globalData: {
bluetoothManager: undefined
},
onLaunch: function () {
this.globalData.bluetoothManager = createBluetoothManager()
},
onShow: function () {
console.log('App Show')
},
onHide: function () {
console.log('App Hide')
},
}
</script>
<!-- index.vue -->
<script>
export default {
data() {
return {
}
},
onLoad() {
getApp().globalData.bluetoothManager.on("statusChange", this.onBluetoothStatusChanged)
},
onUnload() {
getApp().globalData.bluetoothManager.off("statusChange", this.onBluetoothStatusChanged)
},
methods: {
onBluetoothStatusChanged: function(status) {
console.log("onBluetoothStatusChanged", status)
}
}
}
</script>
二.监听系统蓝牙开关,改变蓝牙状态
注: 安卓机型需要有位置权限才能搜索到设备
只需要调用使用uni.onBluetoothAdapterStateChange
即可。
注: uni.onBluetoothAdapterStateChange
只需放在uni.openBluetoothAdapter
调用完成后即可:
- 安卓 12 及以上版本手机,无
搜索附近设备权限
或附近设备
开关未打开 - iOS手机,未打开
微信app访问蓝牙的开关
上述两种情况,调用uni.onBluetoothAdapterStateChange
无法成功添加监听。
...
function initializeBluetooth(): Promise<any> {
this.status = BLUETOOTH_AUTHOR_PENDING
dispatchBluetoothStatus.call(this, this.status)
const scope:string = "scope.bluetooth"
const openBluetoothAdapter = ():Promise<any> => {
return uni.openBluetoothAdapter().catch((err) => {
this.status = BLUETOOTH_ADAPTER_UNAVAILABLE
dispatchBluetoothStatus.call(this, this.status)
return Promise.reject()
}).then((res) => {
this.status = BLUETOOTH_ADAPTER_AVAILABLE
dispatchBluetoothStatus.call(this, this.status)
return Promise.resolve()
}).finally(() => {
//关闭蓝牙适配器状态监听
uni.offBluetoothAdapterStateChange(this.adapterStateChange)
//开启蓝牙适配器状态监听
uni.onBluetoothAdapterStateChange(this.adapterStateChange)
})
}
...
}
...
class BluetoothManagerInstance implements BluetoothManager {
...
private adapterStateChange: (result: any) => void = (res) => {
let available = (res || {}).available || false
let discovering = (res || {}).discovering || false
let bluetoothStatus = BLUETOOTH_ADAPTER_UNAVAILABLE
if(available) {
bluetoothStatus = BLUETOOTH_ADAPTER_AVAILABLE
}
this.status = bluetoothStatus
}
...
destroy(): void {
this.status = BLUETOOTH_AUTHOR_PENDING
//关闭蓝牙适配器状态监听
uni.offBluetoothAdapterStateChange(this.adapterStateChange)
uni.closeBluetoothAdapter().catch(() => undefined)
this.statusCallbacks = []
uni.offAppShow(this.onAppShow)
}
}
三.搜索设备
-
在
uni.onBluetoothDeviceFound
回调了某个设备,则此设备会添加到uni.getBluetoothDevices
接口获取到的数组中 -
只需要在
uni.openBluetoothAdapter
调用成功后,调用uni.startBluetoothDevicesDiscovery
,调用成功后,再通过uni.onBluetoothDeviceFound
开启搜索到设备
的监听,在回调中通过uni.getBluetoothDevices
就可以拿到所有搜索到的设备了。... function startScanBluetoothDevice(): Promise<any> { return uni.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, interval: 1000 }).catch((err) => { return Promise.reject() }).then(() => { //添加搜索到新设备的事件的监听函数 uni.offBluetoothDeviceFound(this.onBluetoothDeviceFound) uni.onBluetoothDeviceFound(this.onBluetoothDeviceFound) return Promise.resolve() }) } function stopScanBluetoothDevice():Promise<any> { //移除搜索到新设备的事件的监听函数 uni.offBluetoothDeviceFound(this.onBluetoothDeviceFound) return uni.stopBluetoothDevicesDiscovery().catch(() => undefined) } function initializeBluetooth(): Promise<any> { ... return uni.authorize({scope: scope}).catch((err) => { return getSetting() }).catch((err) => { this.status = BLUETOOTH_AUTHOR_REJECT dispatchBluetoothStatus.call(this, this.status) return Promise.reject() }).then((res) => { return uni.closeBluetoothAdapter().catch((err) => Promise.resolve()) }).then((res) => { return openBluetoothAdapter() }).then((res) => { return stopScanBluetoothDevice.call(this) }).then((res) => { return startScanBluetoothDevice.call(this) }) } class BluetoothManagerInstance implements BluetoothManager { ... private onBluetoothDeviceFound: (result: any) => void = (result) => { uni.getBluetoothDevices().then((res) => { //所有搜索到的设备 console.log("onBluetoothDeviceFound", res) }) } ... destroy(): void { this.status = BLUETOOTH_AUTHOR_PENDING stopScanBluetoothDevice.call(this) //关闭蓝牙适配器状态监听 uni.offBluetoothAdapterStateChange(this.adapterStateChange) uni.closeBluetoothAdapter().catch(() => undefined) this.statusCallbacks = [] uni.offAppShow(this.onAppShow) } }
-
系统蓝牙开关由
开变关
时,停止搜索,系统蓝牙由关变开
时,重新开始搜索class BluetoothManagerInstance implements BluetoothManager { ... private adapterStateChange: (result: any) => void = (res) => { let available = (res || {}).available || false let discovering = (res || {}).discovering || false let bluetoothStatus = BLUETOOTH_ADAPTER_UNAVAILABLE if(available) { bluetoothStatus = BLUETOOTH_ADAPTER_AVAILABLE } if(this.status != bluetoothStatus) { //蓝牙开关状态改变 if(bluetoothStatus == BLUETOOTH_ADAPTER_AVAILABLE) { //搜索设备 startScanBluetoothDevice.call(this) } else { //停止搜索设备 stopScanBluetoothDevice.call(this) } } this.status = bluetoothStatus } ... }
-
将搜索到的设备排序,最后回传给业务层
... function dispatchDeviceFound(devices: Array<any>) { this.devicesCallbacks.forEach((func) => { func && func(devices) }) } ... class BluetoothManagerInstance implements BluetoothManager { ... private devicesCallbacks: Array<(devices: Array<any>) => void> = []; ... private onBluetoothDeviceFound: (result: any) => void = (result) => { uni.getBluetoothDevices().then((res) => { let devices = (res || {}).devices || [] //排序 devices.sort((item1, item2) => { let name1 = (item1.localName || item1.name).toLowerCase() let name2 = (item2.localName || item2.name).toLowerCase() return name1.localeCompare(name2) }) dispatchDeviceFound.call(this, devices) }) } ... on(type: string, callback: (result: any) => void): void { if(type == "statusChange") { this.statusCallbacks.push(callback) } else if(type == "deviceFound") { this.devicesCallbacks.push(callback) } } off(type: string, callback: (result: any) => void): void { if(type == "statusChange") { let callbackIndex = this.statusCallbacks.findIndex((item) => item == callback) if(callbackIndex == -1) { return } this.statusCallbacks.splice(callbackIndex, 1) } else if(type == "deviceFound") { let callbackIndex = this.devicesCallbacks.findIndex((item) => item == callback) if(callbackIndex == -1) { return } this.devicesCallbacks.splice(callbackIndex, 1) } } destroy(): void { this.status = BLUETOOTH_AUTHOR_PENDING stopScanBluetoothDevice.call(this) //关闭蓝牙适配器状态监听 uni.offBluetoothAdapterStateChange(this.adapterStateChange) uni.closeBluetoothAdapter().catch(() => undefined) this.statusCallbacks = [] this.devicesCallbacks = [] uni.offAppShow(this.onAppShow) } }
<!-- index.vue --> <template> <view class="index-page"> <view class="bluetooth-status">蓝牙状态: {{bluetoothStatus}}</view> <scroll-view scroll-y class="device-list"> <view v-for="(item, index) in devices" class="device-item"> <view class="device-info">设备名称: {{item.localName || item.name || item.deviceId}}</view> <view class="device-info">设备ID: {{item.deviceId}}</view> <view class="device-info">RSSI: {{item.RSSI}}</view> </view> </scroll-view> </view> </template> <script> export default { data() { return { bluetoothStatus: "BLUETOOTH_AUTHOR_PENDING", devices: [] } }, onLoad() { getApp().globalData.bluetoothManager.on("statusChange", this.onBluetoothStatusChanged) getApp().globalData.bluetoothManager.on("deviceFound", this.onDeviceFound) }, onUnload() { getApp().globalData.bluetoothManager.off("statusChange", this.onBluetoothStatusChanged) getApp().globalData.bluetoothManager.off("deviceFound", this.onDeviceFound) }, methods: { onBluetoothStatusChanged: function(status) { this.bluetoothStatus = status }, onDeviceFound: function(devices) { this.devices = devices } } } </script> <style scoped> .index-page { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: stretch; justify-content: center; } .bluetooth-status { font-size: 24rpx; padding: 20rpx 0; margin: 0 20rpx; box-sizing: border-box; flex-shrink: 0; border-bottom: thin solid #f2f2f2; } .device-list { flex: 1; overflow: hidden; } .device-item { display: flex; flex-direction: column; align-items: stretch; justify-content: center; padding: 20rpx 0; margin: 0 20rpx; box-sizing: border-box; background: white; border-bottom: thin solid #f2f2f2; } .device-info { font-size: 28rpx; margin-top: 20rpx; } .device-info:first-child { margin-top: 0; } </style>
四.完整代码
/**
* 蓝牙权限待申请
*/
const BLUETOOTH_AUTHOR_PENDING:string = "BLUETOOTH_AUTHOR_PENDING"
/**
* 蓝牙权限被拒绝
*/
const BLUETOOTH_AUTHOR_REJECT:string = "BLUETOOTH_AUTHOR_REJECT"
/**
* BluetoothAdapter不可用
*/
const BLUETOOTH_ADAPTER_UNAVAILABLE:string = "BLUETOOTH_ADAPTER_UNAVAILABLE"
/**
* 蓝牙适配器可用
*/
const BLUETOOTH_ADAPTER_AVAILABLE:string = "BLUETOOTH_ADAPTER_AVAILABLE"
export interface BluetoothManager {
on(type: string, callback: (status: string) => void): void;
off(type: string, callback: (status: string) => void): void;
destroy(): void;
}
function dispatchDeviceFound(devices: Array<any>) {
this.devicesCallbacks.forEach((func) => {
func && func(devices)
})
}
function dispatchBluetoothStatus(status: string): void {
this.statusCallbacks.forEach((func) => {
func && func(status)
})
}
function startScanBluetoothDevice(): Promise<any> {
return uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
interval: 1000
}).catch((err) => {
return Promise.reject()
}).then(() => {
//添加搜索到新设备的事件的监听函数
uni.offBluetoothDeviceFound(this.onBluetoothDeviceFound)
uni.onBluetoothDeviceFound(this.onBluetoothDeviceFound)
return Promise.resolve()
})
}
function stopScanBluetoothDevice():Promise<any> {
//移除搜索到新设备的事件的监听函数
uni.offBluetoothDeviceFound(this.onBluetoothDeviceFound)
return uni.stopBluetoothDevicesDiscovery().catch(() => undefined)
}
function initializeBluetooth(): Promise<any> {
this.status = BLUETOOTH_AUTHOR_PENDING
dispatchBluetoothStatus.call(this, this.status)
const scope:string = "scope.bluetooth"
const openBluetoothAdapter = ():Promise<any> => {
return uni.openBluetoothAdapter().catch((err) => {
this.status = BLUETOOTH_ADAPTER_UNAVAILABLE
dispatchBluetoothStatus.call(this, this.status)
return Promise.reject()
}).then((res) => {
this.status = BLUETOOTH_ADAPTER_AVAILABLE
dispatchBluetoothStatus.call(this, this.status)
return Promise.resolve()
}).finally(() => {
//关闭蓝牙适配器状态监听
uni.offBluetoothAdapterStateChange(this.adapterStateChange)
//开启蓝牙适配器状态监听
uni.onBluetoothAdapterStateChange(this.adapterStateChange)
})
}
const openSetting = () => {
return uni.showModal({
content: "您需要在设置中开启\"蓝牙\"选项,才可以使用蓝牙哦!",
cancelText: "任性不去",
confirmText: "去设置",
}).then((res) => {
if (res.confirm) {
return new Promise((resolve, reject) => {
this.openSettingResolves.push(resolve)
uni.openSetting().catch((error) => {
reject({
"errMsg": "openSetting:fail system error",
"errno": -2
})
})
}).then((res) => {
let authSetting = res.authSetting || {}
if (authSetting[scope]) {
return Promise.resolve()
} else {
return openSetting()
}
})
} else {
return Promise.reject({
"errMsg": "authorize:fail auth deny",
"errno": 103
})
}
})
}
const getSetting = () => {
return uni.getSetting().catch((error) => {
return Promise.reject({
"errMsg": "authorize:fail system error",
"errno": -2
})
}).then((res) => {
let authSetting = res.authSetting || {}
let keys = Object.keys(authSetting)
if (keys.indexOf(scope) != -1) {
if(!authSetting[scope]) {
return openSetting()
} else {
return Promise.resolve()
}
} else {
return Promise.reject({
"errMsg": "authorize:fail auth canceled",
"errno": -3
})
}
})
}
return uni.authorize({scope: scope}).catch((err) => {
return getSetting()
}).catch((err) => {
this.status = BLUETOOTH_AUTHOR_REJECT
dispatchBluetoothStatus.call(this, this.status)
return Promise.reject()
}).then((res) => {
return uni.closeBluetoothAdapter().catch((err) => Promise.resolve())
}).then((res) => {
return openBluetoothAdapter()
}).then((res) => {
return stopScanBluetoothDevice.call(this)
}).then((res) => {
return startScanBluetoothDevice.call(this)
})
}
class BluetoothManagerInstance implements BluetoothManager {
private status:string;
private statusCallbacks: Array<(status: string) => void> = [];
private openSettingResolves: Array<(result:any) => void> = [];
private devicesCallbacks: Array<(devices: Array<any>) => void> = [];
private onAppShow: (options: any) => void = (options): void => {
uni.getSetting().then((res) => {
if(this.openSettingResolves.length > 0) {
this.openSettingResolves.forEach((resolveFunc) => resolveFunc(res))
this.openSettingResolves = []
} else {
let authSetting = res.authSetting || {}
let keys = Object.keys(authSetting)
if(keys.indexOf("scope.bluetooth") != -1) {
if (!authSetting["scope.bluetooth"]) {
if(this.status != BLUETOOTH_AUTHOR_PENDING) {
this.status = BLUETOOTH_AUTHOR_REJECT
dispatchBluetoothStatus.call(this, this.status)
}
} else {
if(this.status == BLUETOOTH_AUTHOR_REJECT) {
initializeBluetooth.call(this);
}
}
}
}
return Promise.resolve()
}).catch((err) => {
return Promise.resolve()
})
}
private adapterStateChange: (result: any) => void = (res) => {
let available = (res || {}).available || false
let discovering = (res || {}).discovering || false
let bluetoothStatus = BLUETOOTH_ADAPTER_UNAVAILABLE
if(available) {
bluetoothStatus = BLUETOOTH_ADAPTER_AVAILABLE
}
if(this.status != bluetoothStatus) {
//蓝牙开关状态改变
if(bluetoothStatus == BLUETOOTH_ADAPTER_AVAILABLE) {
//搜索设备
startScanBluetoothDevice.call(this)
} else {
//停止搜索设备
stopScanBluetoothDevice.call(this)
}
}
this.status = bluetoothStatus
}
private onBluetoothDeviceFound: (result: any) => void = (result) => {
uni.getBluetoothDevices().then((res) => {
let devices = (res || {}).devices || []
//排序
devices.sort((item1, item2) => {
let name1 = (item1.localName || item1.name).toLowerCase()
let name2 = (item2.localName || item2.name).toLowerCase()
return name1.localeCompare(name2)
})
dispatchDeviceFound.call(this, devices)
})
}
constructor() {
uni.onAppShow(this.onAppShow);
initializeBluetooth.call(this);
}
on(type: string, callback: (result: any) => void): void {
if(type == "statusChange") {
this.statusCallbacks.push(callback)
} else if(type == "deviceFound") {
this.devicesCallbacks.push(callback)
}
}
off(type: string, callback: (result: any) => void): void {
if(type == "statusChange") {
let callbackIndex = this.statusCallbacks.findIndex((item) => item == callback)
if(callbackIndex == -1) {
return
}
this.statusCallbacks.splice(callbackIndex, 1)
} else if(type == "deviceFound") {
let callbackIndex = this.devicesCallbacks.findIndex((item) => item == callback)
if(callbackIndex == -1) {
return
}
this.devicesCallbacks.splice(callbackIndex, 1)
}
}
destroy(): void {
this.status = BLUETOOTH_AUTHOR_PENDING
stopScanBluetoothDevice.call(this)
//关闭蓝牙适配器状态监听
uni.offBluetoothAdapterStateChange(this.adapterStateChange)
uni.closeBluetoothAdapter().catch(() => undefined)
this.statusCallbacks = []
this.devicesCallbacks = []
uni.offAppShow(this.onAppShow)
}
}
export const createBluetoothManager = ():BluetoothManager => {
return new BluetoothManagerInstance();
}