【Android】蓝牙快速开发工具包-入门级
开头语
方便快速开发蓝牙,封装常用的操作。
需要以下三个权限:
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 -> 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<BluetoothDevice> { val list = ArrayList<BluetoothDevice>() 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
更多相关文章
- ShareSDK for Android(安卓)> 第三方登录 > 授权与取消授权
- 【Android】Android(安卓)手机连接 Win7 蓝牙
- Android(安卓)Https 双向认证
- Android(安卓)蓝牙开发浅析
- Android(安卓)客户端通过内置API(HttpClient) 访问 服务器(用Spri
- Android关于GET和POST发送请求
- Lync之android客户端内网登陆
- android客户端版本检测更新,服务下载,通知栏显示
- 超酷的 gankIO 客户端