【Android】蓝牙快速开发工具包-入门级

Gloria ·
更新时间:2024-09-20
· 712 次阅读

开头语

方便快速开发蓝牙,封装常用的操作。

需要以下三个权限:

android.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN android.permission.ACCESS_FINE_LOCATION 定位权限是由于6.0之后扫描设备需要 经典蓝牙 服务端 开启服务端

首先,实例化一个BluetoothClassicalServer。该对象的构造函数需要两个必要参数:

serverName,服务端名称,可随意。 uuid,用于标识服务端,客户端需要使用相同的uuid才可以连接上来。 class BluetoothClassicalServer( val serverName: String, val uuid: UUID ) : CoroutineScope {}

然后,设置监听器,用于监听设备连接状态改变。

server.listener = object:ClassicalServerListener(){ override fun onDeviceConnected(client: BluetoothClassicalServer.Client) {} override fun onFail(errorMsg: String) {} } /*服务端监听器,监听设备连接以及服务端状态*/ open class ClassicalServerListener { /** * 客户端设备已连接,可能会多次被调用 * @param client 客户端 */ open fun onDeviceConnected(client: BluetoothClassicalServer.Client) {} /** * 服务端发生异常 * @param errorMsg 错误信息 */ open fun onFail(errorMsg: String) {} }

最后,调用start()方法开始监听客户端连接。

start()方法使用了一个在IO线程的协程去等待客户端的连接。如果有设备连接上来,那么会切换到主线程回调ClassicalServerListener

/** * 开启服务端 * @param timeout 超时时间,-1则表示无限 */ fun start(timeout: Int = -1) { if (isRunning) return serverSocket = adapter.listenUsingInsecureRfcommWithServiceRecord(serverName, uuid) // 开启一个IO线程的协程,去监听客户端设备连接 launch(Dispatchers.IO) { try { isRunning = true // serverSocket.accept()是阻塞方法,会一直阻塞直到有客户端连接上来或超时 val clientSocket = serverSocket!!.accept(timeout) addClient(clientSocket) start()// 继续监听连接 Log.d("d","监听服务端") } catch (e: Exception) { // 这里的异常可能是超时、也可能是手动关闭服务端导致的 if (isRunning) { // 超时等系统异常 notifyListenerError("发生异常:${e.message}") } isRunning = false serverSocket?.close() } } } 监听客户端消息

当客户端连接成功后,为该客户端设置一个消息监听器,监听来自客户端的消息。

client.messageListener = object : BluetoothMessageListener() { override fun onFail(errorMsg: String) {} override fun onReceiveMsg(msg: ByteArray) {} } /** * Create by AndroidStudio * Author: pd * Time: 2020/4/5 09:18 * 消息监听器,监听来自客户端的消息以及客户端状态 */ open class BluetoothMessageListener { /** * 接收到客户端传来的消息 * @param msg */ open fun onReceiveMsg(msg:ByteArray){} /** * 客户端发生异常 * @param errorMsg 错误信息 */ open fun onFail(errorMsg:String){} } 向客户端发送消息

调用client.send()。该方法接收一个byte数组。

/** * 向该客户端发送消息 * @param msg 要发送的消息 */ fun send(msg: ByteArray) { if (!socket.isConnected) { notifyListenerError("连接已断开") } else { launch(Dispatchers.IO) {// 切换到IO线程 try { ous.write(msg) } catch (e: Exception) { // 可能在发送信息的时候,与客户端断开连接 notifyListenerError("发生异常:${e.message}") disConnect() } } } } 关闭服务端

调用server.stop()。该方法只是关闭服务端监听,即不再允许有新的客户端连接到服务端。但是,已经连接的客户端依然可以进行通信。

/** * 关闭服务端,但是已经连接的客户端依然允许通信 */ fun stop() { isRunning = false serverSocket?.close() } 断开与客户端的连接

调用client.disConnect()

/** * 关闭与客户端的连接 */ fun disConnect() { socket.close() messageListener = null // 从服务端设备列表中移除 server.removeClient(this) } 客户端 扫描服务端

调用BtManager.scanDevice。该方法接收3个参数。

经典蓝牙的扫描需要在Application.onCreate()中调用BtManager.init()。因为涉及到广播注册,因此使用Application以防止忘记解注册导致内存泄漏。

/** * 扫描设备 * @param listener 扫描监听 * @param timeout 扫描超时时间,最低默认为10秒 * @param type 扫描类型 * @see BluetoothType */ fun scanDevice( listener: ScanListener? = null, timeout: Int = 10, type: BluetoothType = BluetoothType.CLASSICAL ) {} 连接服务端

首先,实例化BluetoothClassicalClient()。接收两个参数。

class BluetoothClassicalClient( // 服务端设备 val serverDevice: BluetoothDevice, // 服务端uuid,需要和服务端开启时使用的uuid一致 private val uuid: UUID ) : CoroutineScope {}

然后,设置客户端监听器,监听和服务端的连接状态。

client!!.clientListener = object : ClassicalClientListener() { // 与服务端连接成功 override fun onConnected() {} // 客户端出现异常 override fun onFail(errorMsg: String) {} }

最后,调用client.connectServer()。正式发起与服务端的连接。

/** * 开始连接服务端 */ fun connectServer() { launch(Dispatchers.IO) { val socket = serverDevice.createInsecureRfcommSocketToServiceRecord(uuid) try { socket.connect()// 阻塞直到连接成功或者出现异常 notifyListenerConnected() serverSocket = socket // 连接成功后开启消息轮询 startListenMsg() } catch (e: Exception) { // 可能在连接的时候,服务端关闭了 notifyListenerError("发生异常:${e.message}") socket.close() } } } 监听服务端消息

当与服务端连接成功后,设置消息监听器。

// 连接成功后设置消息监听器 client!!.messageListener = object : BluetoothMessageListener(){ // 与服务端连接出现异常 override fun onFail(errorMsg: String) {} // 接收到服务端的消息 override fun onReceiveMsg(msg: ByteArray) {} } 向服务端发送消息

调用client.send()。接收一个byte数组。

/** * 向服务端发送消息 * @param data */ fun send(data: ByteArray) { launch(Dispatchers.IO) {// 切换到IO线程 if (serverSocket != null) { if (serverSocket!!.isConnected) { try { serverSocket!!.outputStream.write(data) } catch (e: Exception) { notifyMessageFail("发生异常:${e.message}") serverSocket!!.close() } } } } } 断开与服务端的连接

调用client.disConnect()

/** * 关闭与服务端的连接 */ fun disConnect() { serverSocket?.close() messageListener = null } 低功耗蓝牙BLE 服务端 开启服务端

首先,实例化BluetoothLeServer

class BluetoothLeServer( private val context: Context ) : CoroutineScope {}

然后,设置监听器,该监听器可监听客户端连接状态、客户端请求等。

server.listener = object : BleServerListener() { /** * 客户端设备连接状态改变 * @param device 客户端设备 * @param status 连接状态 * @param isConnected true已连接,false未连接 */ override fun onDeviceConnectionChange( device: BluetoothDevice?, status: Int, isConnected: Boolean ) { } /** * 读特性请求 * @param request 请求体 */ override fun onCharacteristicReadRequest(request: CharacteristicRequest) { } /** * 写特性请求 * @param request 请求体 */ override fun onCharacteristicWriteRequest(request: CharacteristicWriteRequest) { } /** * 新增服务回调 * @param service 新增的服务 * @param isSuccess 是否新增成功 */ override fun onServiceAdded(service: BluetoothGattService?, isSuccess: Boolean) { } }

最后,真正的开启服务端。server.start()

/** * 开启BLE服务端 * @return true表示服务端成功开启/false表示开启失败 */ fun start(): Boolean { gattServer = bluetoothManager.openGattServer(context, gattCallback) gattServer?.clearServices() return gattServer != null } 开启广播

调用server.startAdv()。接收3个必要参数以及1个可空参数。

/** * 开启广播才能被BLE扫描模式搜索到该设备 * @param advertiseSettings 广播设置 * @param advertiseData 广播内容 * @param scanResponse 广播被扫描后回复的内容 * @param callback 回调 */ fun startAdv( advertiseSettings: AdvertiseSettings, advertiseData: AdvertiseData, scanResponse: AdvertiseData? = null, callback: AdvertiseCallback ) { bluetoothAdapter.bluetoothLeAdvertiser.startAdvertising( advertiseSettings, advertiseData, callback ) isAdving = true } 新增服务

调用server.addService()

/** * 下一次addService必须要等上一次addService回调了onServiceAdded()之后才能再调用 * @param uuid 服务uuid */ fun addService(uuid: UUID) { val service = BluetoothGattService(uuid,BluetoothGattService.SERVICE_TYPE_PRIMARY) addService(service) } 新增特性

首先,调用server.buildCharacteristic()构造一个特性。该方法接收一个必要参数以及4个可空参数。

/** * 创建特性 * @param characteristicUUID 特性UUID * @param readable 特性是否可读,默认否 * @param writable 特性是否可写,默认否 * @param writeNoResponse 特性是否支持不用回复的写入,默认否 * @param notify 特性是否可通知,默认为否 */ fun buildCharacteristic( characteristicUUID: UUID, readable: Boolean = false, writable: Boolean = false, writeNoResponse: Boolean = false, notify: Boolean = false ): BluetoothGattCharacteristic { var permission = 0x00 if (readable) permission = permission or BluetoothGattCharacteristic.PERMISSION_READ if (writable) permission = permission or BluetoothGattCharacteristic.PERMISSION_WRITE var property = 0x00 if (readable) property = property or BluetoothGattCharacteristic.PROPERTY_READ if (writable) property = property or BluetoothGattCharacteristic.PROPERTY_WRITE if (writeNoResponse) property = property or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE if (notify) property = property or BluetoothGattCharacteristic.PROPERTY_NOTIFY return BluetoothGattCharacteristic(characteristicUUID, property, permission) }

然后,调用server.addCharacteristicToService(),方法接收两个必要参数。

/** * 向已存在的服务添加特性 * @param serviceUUID 服务的UUID * @param characteristic 要添加的特性 * @return true表示添加成功,false表示失败 */ fun addCharacteristicToService( serviceUUID: UUID, characteristic: BluetoothGattCharacteristic ): Boolean { val service = gattServer?.getService(serviceUUID) ?: return false gattServer?.removeService(service) service.addCharacteristic(characteristic) gattServer?.addService(service) return true } 回复客户端请求

首先,实例化一个回复类BleServerResponse

class BleServerResponse( val request: CharacteristicRequest,// 要回复的请求体 val status: Int,// 状态码,0表示该请求成功 val data: ByteArray,// 回复的内容 val offset: Int = 0// 内容偏移量 )

然后,调用server.sendResponse()。方法接收一个必要参数。

/** * 回复请求 * @param response 回复对象 * @return true表示回复成功,false表示回复失败 */ fun sendResponse(response: BleServerResponse): Boolean { val result = gattServer?.sendResponse( response.request.device, response.request.requestId, response.status, response.offset, response.data ) ?: false // 如果回复成功的话,那么清除这次请求 if (result) lastRequest = null return result } 主动通知客户端

调用server.notifyCharacteristic(),方法接收2个必要参数和1个可空参数。

/** * 服务端主动通知客户端特性内容有变化 * @param characteristic 内容变化的特性 * @param device 客户端设备 * @param confirm 默认为false */ fun notifyCharacteristic( characteristic: BluetoothGattCharacteristic, device: BluetoothDevice, confirm: Boolean = false ): Boolean { return gattServer?.notifyCharacteristicChanged(device, characteristic, confirm) ?: false } 关闭广播

调用server.stopAdv()。方法接收1个必要参数。

/** * 关闭广播,可能导致设备断开连接 * @param callback 开启广播的时候设置的回调 */ fun stopAdv(callback: AdvertiseCallback) { bluetoothAdapter.bluetoothLeAdvertiser.stopAdvertising(callback) isAdving = false } 关闭服务端

调用server.stop()

/** * 关闭BLE服务端 */ fun stop() { gattServer?.close() gattServer = null } 客户端 扫描服务端

调用BtManager.scanDevice()。如果只有BLE扫描的话,可以不用在Application.onCreate()中调用BtManager.init()

/** * 扫描设备 * @param listener 扫描监听 * @param timeout 扫描超时时间,最低默认为10秒 * @param type 扫描类型,不传则默认为经典蓝牙 * @see BluetoothType */ fun scanDevice( listener: ScanListener? = null, timeout: Int = 10, type: BluetoothType = BluetoothType.CLASSICAL ) {} 连接服务端

首先,实例化BluetoothLeClient

class BluetoothLeClient( val serverDevice: BluetoothDevice,// 服务端设备 private val context: Context// 上下文 ) : CoroutineScope {}

然后,调用client.connectServer()发起连接。

/** * 连接服务端 * @param gattCallback 回调监听 * @return false表示连接失败,可能当前设备不支持BLE,不是服务端不支持 */ fun connectServer(gattCallback: BluetoothGattCallback? = null): Boolean { if (gattCallback != null) callback = gattCallback server = serverDevice.connectGatt(context, false, callbackPoxy) server?.discoverServices() if (server != null) isConnected = true return server != null } 查询服务

调用server.checkService()。查询结果在开启服务端时设置的回调监听onServicesDiscovered()中接收。

/** * 查询服务端支持的服务列表,结果在回调onServicesDiscovered */ fun checkService() { if (server == null) { callback.onServicesDiscovered(server, -1) } else { server?.discoverServices() } }

callback.onServicesDiscovered()中的status = 0时。调用gatt.getService()即可获取到服务端支持的服务列表。

val callback = object : BluetoothGattCallback() { override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) { // 这里是非主线程啊!!! if (status == 0) { val list = gatt!!.services } } } 发送读特性请求

调用client.readCharacteristic()

/** * 发送读特性请求 * @param characteristic 要读取的特性 * @return true表示发送请求成功,false表示发送请求失败 */ fun readCharacteristic(characteristic: BluetoothGattCharacteristic): Boolean { return server?.readCharacteristic(characteristic) ?: false } 发送写特性请求

调用client.writeCharactetistic()

/** * 发送写特性请求 * @param characteristic 要写入的特性 * @param data 要写入的内容 * @return true表示发送请求成功,false表示发送请求失败 */ fun writeCharacteristic( characteristic: BluetoothGattCharacteristic, data:ByteArray ): Boolean { characteristic.value = data return server?.writeCharacteristic(characteristic) ?: false } 注册特性通知

当注册了特性通知之后,服务端才能通过通知主动向客户端发送消息。否则,即使服务端发送了通知,但是由于客户端没有注册,依然无法收到。

调用client.regCharacteristicNotify()

/** * 注册特性通知 * @param characteristic 希望接收通知的特性 * @return true表示注册成功,false表示注册失败 */ fun regCharacteristicNotify(characteristic: BluetoothGattCharacteristic): Boolean { return server?.setCharacteristicNotification(characteristic, true) ?: false } 断开与服务端的连接

调用client.disconnectServer()

/** * 断开和服务端的连接 */ fun disconnectServer() { server?.disconnect() isConnected = false } 公共工具

一些经典蓝牙和Ble都需要用到的方法,统一放在公共工具类BtManager

经典蓝牙扫描初始化

由于经典蓝牙的扫描结果是通过广播的形式传递过来的。因此需要注册一下广播。

/** * 因为需要注册广播,所以在application中初始化一下 * 如果不需要经典蓝牙的话不调用也不影响 */ fun init(application: Application) { this.application = application val filter = IntentFilter() filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED) filter.addAction(BluetoothDevice.ACTION_FOUND) this.application!!.registerReceiver(receiver, filter) } 扫描设备

扫描经典蓝牙设备时,一定要先确定是否调用了BtManager.init()

如果无法扫描到任何设备,请确认App拥有定位权限。

/** * 扫描设备 * @param listener 扫描监听 * @param timeout 扫描超时时间,最低默认为10秒,最高为60秒 * @param type 扫描类型 * @see BluetoothType */ fun scanDevice( listener: ScanListener? = null, timeout: Int = 10, type: BluetoothType = BluetoothType.CLASSICAL ) { stopJob?.cancel() // 超时时间 val realTimeout = when { timeout 10 * 1000 timeout > 60 -> 60 * 1000 else -> timeout * 1000 } // 超时后将结束扫描 stopJob = launch(Dispatchers.Default) { delay(realTimeout.toLong()) stopScan() stopJob = null } stopScan() deviceList.clear() isScanning = true this.scanListener = listener when (type) { BluetoothType.CLASSICAL -> scanClassicalDevice() BluetoothType.LOW_ENG -> scanBleDevice() } } 结束扫描设备 /** * 结束设备扫描 */ fun stopScan() { blAdapter.cancelDiscovery() blAdapter.bluetoothLeScanner.stopScan(bleCallBack) isScanning = false scanListener = null stopJob?.cancel() stopJob = null } 查看当前手机已配对的设备 /** * 查找当前已绑定的设备,经典蓝牙 */ fun getBondedDevice(): ArrayList { val list = ArrayList() list.addAll(blAdapter.bondedDevices) return list } 判断当前设备蓝牙是否已打开 /** * 蓝牙是否打开 */ fun isOn(): Boolean { return blAdapter.isEnabled } 获取当前设备蓝牙名称 /** * 获取蓝牙名称 */ fun getName(): String { return blAdapter.name } 判断当前设备是否支持蓝牙 /** * 当前设备是否支持蓝牙 */ fun isSupport(): Boolean { return blAdapter != null } 源码连接

所有源码已打包上传至github。https://github.com/d745282469/BlueToothTool


作者:大东Pd



工具 开发工具 Android 蓝牙

需要 登录 后方可回复, 如果你还没有账号请 注册新账号