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

开头语

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

需要以下三个权限:

  1. android.permission.BLUETOOTH
  2. android.permission.BLUETOOTH_ADMIN
  3. android.permission.ACCESS_FINE_LOCATION 定位权限是由于6.0之后扫描设备需要

经典蓝牙

服务端
开启服务端

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

  1. serverName,服务端名称,可随意。
  2. 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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值