1 传统蓝牙与低功耗蓝牙

传统蓝牙也叫经典蓝牙,经典蓝牙模块泛指支持蓝牙协议4.0以下的模块,有v1.1/1.2/2.0/2.1/3.0。经典蓝牙支持音频(HFP/HSP, A2DP)和数据(SPP, HID, OPP, PBAP等)两大类协议,通常用于数据量较大的传输,比如蓝牙耳机传递音乐,比如汽车的蓝牙免提通讯以及车载蓝牙娱乐系统,经典蓝牙由于功耗较大,逐渐在移动互联网中淘汰。

低功耗蓝牙模块指支持蓝牙协议4.0及以上的模块,Bluetooth Low Energy,简称BLE。低功耗蓝牙功耗低,非常省电,但低功耗蓝牙不支持音频协议,适用于传输数据量较少的应用,比如智能家居,血压计,温度传感器等。

低功耗蓝牙也分为单模芯片和双模芯片两种类型,双模芯片中和了两种蓝牙模块的优缺点,低功耗的同时还能传输音频。

2 申请权限

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 搜索蓝牙需要,因为蓝牙可以被用来定位,所以需要定位权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

3 蓝牙基本操作

3.1 获取蓝牙适配器
// 蓝牙适配器
private val mBluetoothAdapter: BluetoothAdapter by lazy {
    // 初始化蓝牙适配器
    val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    bluetoothManager.adapter
}
3.2 判断是否开启蓝牙
// 是否开启蓝牙
fun isBluetoothEnabled():Boolean{
    return mBluetoothAdapter.isEnabled
}
3.3 开启蓝牙
// 开启蓝牙
fun openBluetooth():BluetoothHelper{
    if (!mBluetoothAdapter.isEnabled) {
        if(!mBluetoothAdapter.enable()){// 尝试开启蓝牙,返回值为是否成功开启蓝牙
            showToast("建议允许开启蓝牙操作")
        }
    }
    return this
}
3.4 检测蓝牙状态变化的广播接收器
// 蓝牙状态改变广播接收器
private val mBluetoothStateReceiver by lazy{
    object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent) {
            when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
                BluetoothAdapter.STATE_ON -> {showToast("蓝牙已打开")}
                BluetoothAdapter.STATE_OFF -> {showToast("蓝牙已关闭")}
                BluetoothAdapter.STATE_TURNING_ON -> {showToast("蓝牙正在打开")}
                BluetoothAdapter.STATE_TURNING_OFF -> {showToast("蓝牙正在关闭")}
            }
        }
    }
}
3.5 扫描周围蓝牙设备
// 搜索蓝牙设备
fun searchDevices():BluetoothHelper{
    if (mBluetoothAdapter.isDiscovering){// 如果正在搜索则取消搜索
        mBluetoothAdapter.cancelDiscovery()
    }else{
        mBluetoothAdapter.startDiscovery()
    }
    return this
}
3.6 停止扫描蓝牙设备
// 停止扫描蓝牙设备
fun stopSearchDevices():BluetoothHelper{
    if (mBluetoothAdapter.isDiscovering){
        mBluetoothAdapter.cancelDiscovery()
    }
    return this
}
3.7 扫描到蓝牙设备的广播接收器
// 蓝牙扫描广播接收器
private val mBluetoothScanReceiver by lazy{
    object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent) {
            val action = intent.action
            if (BluetoothDevice.ACTION_FOUND == action) {// 扫描到蓝牙设备
                // 获取扫描到的设备
                val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
                // 设备相关信息
                // device.name?:设备名
                // device.address:设备MAC地址
                // device.bluetoothClass.majorDeviceClass:设备类型
            }else if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {// 扫描到已经被连接的蓝牙设备(附近已经建立连接的蓝牙设备,该连接不一定是和本中央设备)
                val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
            }
        }
    }
}

4 蓝牙通讯

4.1 UUID

UUID(Universally Unique Identifier)是一个十六进制数字,是一种唯一标识码,也是一种软件构建标准。一个UUID有128bit(位),即16个字节。由于128位太长了使用不方便,因此通常会预设共通的部分,蓝牙联盟统一预设的UUID是00000000-0000-1000-8000-00805F9B34FB,我们称它为蓝牙Base UUID。有了蓝牙Base UUID就可以使用16bit或者32bit来表示实际长度为128bit的UUID了,具体的转换方式是,将16bit或32bit的UUID左移96bit后加上Base UUID就能得到实际的UUID。

4.2 服务、特征、属性与描述符等概念

简单描述服务、特征和属性间的关系,假设把一个蓝牙设备比作一个公司,公司有若干个部门,每一个部门就是一个服务。每个部门有若干个组员,每个组员就是一个特征,每个组员会不同的技能,每个技能就是一个属性。当我们需要完成一项任务时,就需要将任务交个对应部门的对应员工,也就是说找到蓝牙设备的某个服务里的某个特征。公司为了标识每个部门和部员,因此给定了部门编号和员工编号,也就是服务UUID和特征UUID。

Descriptor是描述符,被用来定义特征的属性,描述符比起属性则更为详尽。

中央设备,指的是客户端也叫主机或中心设备,通常指代手机。中央设备可以扫描并连接多个外围设备,外围设备即蓝牙设备。

android 蓝牙和低功耗蓝牙开发区别 低功耗蓝牙功耗_低功耗蓝牙

4.3 基于GATT发现服务获取UUID

低功耗蓝牙设备使用的是GATT(Generic Attribute Profile)蓝牙协议,该协议定义了基于Service和Characteristic的蓝牙通信方式。需要注意的是GATT在连接时是独占的,一旦两个设备基于GATT连接后,它们就会停止对外发送蓝牙广播,因此其他蓝牙设备是无法搜索到已经基于GATT连接的蓝牙设备了。

4.3.1 GATT连接(即连接蓝牙设备)
// 蓝牙gatt
var mBluetoothGatt: BluetoothGatt? = null
// gatt连接蓝牙设备
fun connect(device: BluetoothDevice, context: Context) {
    mBluetoothGatt = device.connectGatt(context,false, mGattCallback)
}
4.3.2 GATT连接时传入的回调
// gatt连接回调
private val mGattCallback by lazy{
    object: BluetoothGattCallback() {
        // 连接状态改变时回调
        override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
            super.onConnectionStateChange(gatt, status, newState)
            if (newState == BluetoothProfile.STATE_CONNECTED) {// 成功连接
                mBluetoothGatt?.discoverServices() // 调用发现服务(即获取到蓝牙设备所提供的所有服务)
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 断开连接
            }
        }

        // 调用发现服务时回调
        override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
            super.onServicesDiscovered(gatt, status)
            if (status == BluetoothGatt.GATT_SUCCESS) {// 当前仍处于连接状态的话
                val bluetoothGattServices = gatt!!.services // 获取蓝牙设备提供的所有服务
                // 遍历所有服务
                bluetoothGattServices.forEachIndexed { index, bluetoothGattService ->
                    val uuid = bluetoothGattService.uuid // 获取到此服务的UUID
                    val bluetoothGattCharacteristics = bluetoothGattService.characteristics // 获取到此服务的所有特征
                    Log.d(TAG, "服务${index+1}的UUID为:$uuid")
                    Log.d(TAG, "服务${index+1}有以下特征:")
                    // 遍历该服务的所有特征
                    bluetoothGattCharacteristics.forEachIndexed { index, bluetoothGattCharacteristic ->
                        Log.d(TAG, "  特征${index+1}的UUID:${bluetoothGattCharacteristic.uuid}") // 输出当前特征的UUID
                        val charaProp = bluetoothGattCharacteristic.properties// 获取当前特征的所有属性
                        // bluetoothGattCharacteristic.descriptors // 获取当前特征的所有描述符
                        if (charaProp and BluetoothGattCharacteristic.PROPERTY_READ > 0) {
                            Log.d(TAG, "    特征${index+1}有read属性")
                        }
                        if (charaProp and BluetoothGattCharacteristic.PROPERTY_WRITE > 0) {
                            Log.d(TAG, "    特征${index+1}有write属性")
                        }
                        if (charaProp and BluetoothGattCharacteristic.PROPERTY_NOTIFY > 0) {
                            Log.d(TAG, "    特征${index+1}有notify属性")
                        }
                        if (charaProp and BluetoothGattCharacteristic.PROPERTY_INDICATE > 0) {
                            Log.d(TAG, "    特征${index+1}有indicate属性")
                        }
                    }
                }
            }
        }
        // 读数据时回调
        override fun onCharacteristicRead(
            gatt: BluetoothGatt?,
            characteristic: BluetoothGattCharacteristic?,
            status: Int
        ) {}
        // 写数据时回调
        override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
            super.onCharacteristicWrite(gatt, characteristic, status)
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // 发送成功
            } else {
                // 发送失败
            }
        }
        // 远程数据改变回调
        override fun onCharacteristicChanged(
            gatt: BluetoothGatt?,
            characteristic: BluetoothGattCharacteristic?
        ) {
            super.onCharacteristicChanged(gatt, characteristic)
            characteristic?.apply {// 发生数据变化的特征
                Log.d(TAG, "onCharacteristicChanged: ${characteristic.uuid}")
            }
        }
    }
}

一个特征拥有一至多个属性,公有四个属性。

属性名

说明

read

该特征具有读取数据的能力,可以通过该特征从蓝牙设备获取数据

write

该特征具有写入数据的能力,可以通过该特征向蓝牙设备写入数据

notify

蓝牙设备可以通过此特征主动向我们发送数据

indicate

蓝牙设备可以通过此特征向我们建立一个可靠连接,意思就是向我们发送数据后我们会应答确认一次,以此保证连接的可靠性

4.3.3 向蓝牙设备发送数据

我们可以通过具有read属性的特征向蓝牙设备发送数据,每个特征对应的功能是不同的,因此要知道蓝牙设备提供的每个特征对应什么功能。可以通过UUID的前缀简单判断部分特征的功能。

查询链接:

https://www.bluetooth.com/specifications/assigned-numbers/

找到指定特征后,通过此特征向蓝牙设备发送数据

//蓝牙Gatt连接服务
private var mBluetoothGatt: BluetoothGatt? = null

//某个特征
private var mBluetoothGattCharacteristic:BluetoothGattCharacteristic? = null

//发送数据
fun sendMess(mess:String){
    mBluetoothGattCharacteristic?.setValue(mess)// 修改此特征的值
    mBluetoothGatt?.writeCharacteristic(mBluetoothGattCharacteristic) // 写入此特征来向蓝牙设备发送数据
}