方便快速开发蓝牙,封装常用的操作。
需要以下三个权限:
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.ACCESS_FINE_LOCATION
定位权限是由于6.0之后扫描设备需要
经典蓝牙
服务端
开启服务端
首先,实例化一个BluetoothClassicalServer
。该对象的构造函数需要两个必要参数:
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