BLE开发
BLE的出现原因
传统蓝牙设备的待机耗电量大一-直是为人所诟病的缺陷之一,这与传统蓝牙技术动辄采用16~ 32个频道进行广播不无关系,而低功耗蓝牙芯片仅使用了3个广播通道,且每次广播时射频的开启时间也由传统的22.5ms减少到0.6-1.2ms, 这两个协议规范上的改变显然大大降低了因为广播数据导致的待机功耗;此外低功耗蓝牙芯片设计了用深度睡眠状态来替换传统蓝牙的空闲状态,在深度睡眠状态下,主机长时间处于超低的负载循环(DutyCycle)状态,只在需要运作时由控制器来启动,因主机较控制器消耗更多的能源,因此这样的设计也节省了最多的能源;在深度睡眠状态下,协议也针对此通讯模式进行了优化,数据发送间隔时间也增加到0.5~4s,传感器类应用程序发送的数据量较平常要少很多,而且所有连接均采用先进的嗅探性次额定(Sniff-SubraTIng)功能模式,因此此时的射频能耗几乎可以忽略不计,综合以上因素,低功耗蓝牙芯片的待机功耗较传统蓝牙大大减少。
版本迭代变化
- Android 4.3 支持BLE
- Android 5.0 增加从机角色 MTU增加
- Android 6.0需要定位权限
代码
XML权限
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
<!-- android M 以上版本获取周边蓝牙设备必须定位权限,动态申请 定位权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
核心类
-
BluetoothAdapter 蓝牙适配器
-
BluetoothLeScanner 蓝牙扫描类
startScan() stopScanI() flushPendingScanResults() -
ScanFilter 蓝色过滤条件
-
ScanSettings 蓝牙扫描设置
-
ScanCallback 蓝牙扫描回调
onScanResult onBatchScanResults onScanFailed -
ScanResult 蓝牙扫描结果
-
BluetoothDevice 蓝牙设备
基础代码
Server
初始化就是设置基础的Setting, 对外暴露读写的UUID 和特征值
一般UUID都是组内约定好 ,或者去查蓝牙官网定好的UUID 分类处理
private lateinit var bluetoothManager: BluetoothManager private lateinit var bluetoothAdapter: BluetoothAdapter private lateinit var openServer:BluetoothGattServer private lateinit var read:BluetoothGattCharacteristic private lateinit var okdevice: BluetoothDevice // 当前连接的设备 bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = bluetoothManager.adapter if (!bluetoothAdapter.isEnabled) { bluetoothAdapter.enable() } // BLE公布的核心要素设置 val settings = AdvertiseSettings.Builder() .setConnectable(true) // 打开 可连接的 /连接性 .setTimeout(0) // 不需要 超时时间 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) // 传输功率-使用高功率,这对应的最大可见范围 .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) // 设置公布模式为,在低延迟的 .build() // 统一构建 // BLE公布数据的设置 val advertiseData = AdvertiseData.Builder() .setIncludeDeviceName(true) // 设置是否在发布报文中包含设备名 .setIncludeTxPowerLevel(true) // 发送报文中是否包含传输功率 .build() // 统一构建 bluetoothAdapter.name = "LeoBleTest" bluetoothLeAdvertiser.startAdvertising( settings, advertiseData, object : AdvertiseCallback() { override fun onStartSuccess(settingsInEffect: AdvertiseSettings) { super.onStartSuccess(settingsInEffect) val gattService = BluetoothGattService( UUID_SERVER, BluetoothGattService.SERVICE_TYPE_PRIMARY ) // 只读的特征值 read = BluetoothGattCharacteristic( UUID_CHAR_READ, BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ ) // 只写的特征值 val characteristicWrite = BluetoothGattCharacteristic( UUID_CHAR_WRITE, BluetoothGattCharacteristic.PROPERTY_WRITE or BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_WRITE or BluetoothGattCharacteristic.PERMISSION_READ ) // 将特征值添加至服务里 gattService.addCharacteristic(read) gattService.addCharacteristic(characteristicWrite) // 监听客户端的连接 openServer = bluetoothManager.openGattServer(this@ServerActivity, object : BluetoothGattServerCallback() { //这里处理链接 override fun onConnectionStateChange( device2: BluetoothDevice?, status: Int, newState: Int ) { //todo 处理链接和断开 super.onConnectionStateChange(device2, status, newState) if(newState==BluetoothProfile.STATE_CONNECTED){ okdevice=device2!!//将链接过来的device保存 } } //收到客户端写的消息 override fun onCharacteristicWriteRequest( device: BluetoothDevice?, requestId: Int, characteristic: BluetoothGattCharacteristic?, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray? ) { super.onCharacteristicWriteRequest( device, requestId, characteristic, preparedWrite, responseNeeded, offset, value ) ///收到客户端发来的消息 Log.e(TAG, "收到客户端发来的消息: ${value.toString()}" ) // 反馈:告诉客户端发送成功 openServer.sendResponse( device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic!!.value ) } }) //开启成功 添加 对外广播 openServer.addService(gattService) } override fun onStartFailure(errorCode: Int) { super.onStartFailure(errorCode) //开启失败 } }) fun send(){ //对客户端发送消息 调用 notifyCharacteristicChanged 需要用sever 传入参数分别为绑定的device 和读的特征 //对于客户端来说 调用的就是服务端的读 read.value = "777".toByteArray() openServer.notifyCharacteristicChanged( okdevice, read, false ) } 扫描
扫描一定有个时间限制,当你点击链接过去的时候,一定关掉蓝牙扫描,第一消耗性能,第二避免卡死,所以下面代码做了5000ms的延迟关闭
bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = bluetoothManager.adapter bluetoothAdapter.bluetoothLeScanner.startScan(object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { super.onScanResult(callbackType, result) mAdapter.add(0, BleListItem(result.device, result.rssi)) } })} btnScan.postDelayed({ stopScan() },5000) 连接和发送数据
val devices = intent.getParcelableExtra("device") as BluetoothDevice? //连接服务端 bluetoothGatt = devices?.connectGatt(this@CLinkActivity, false, object : BluetoothGattCallback() { //链接或者断开 override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int ) { super.onConnectionStateChange(gatt, status, newState) when (newState) { //链接成功 BluetoothProfile.STATE_CONNECTED -> { gatt.discoverServices() Log.e(TAG, "onConnectionStateChange:STATE_CONNECTED ") } //断开 BluetoothProfile.STATE_DISCONNECTED -> { Log.e(TAG, "onConnectionStateChange:STATE_DISCONNECTED ") } } } //发现服务端特征值 也就说 服务端已经链接上了 override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) { super.onServicesDiscovered(gatt, status) // 设置读特征值的监听,接收服务端发送的数据 这里的UUID都是预先设置好的 val service = bluetoothGatt!!.getService(UUID_SERVER) val characteristic = service.getCharacteristic(UUID_CHAR_READ) //这里一定要设置监听 为true 不然消息回不来了 val b = bluetoothGatt!!.setCharacteristicNotification(characteristic, true) } //这里注意服务端回调 会回调两个参数的方法 而不是下面三个的 override fun onCharacteristicChanged( gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic? ) { super.onCharacteristicChanged(gatt, characteristic) runOnUiThread { Toast.makeText(this@CLinkActivity,"收到服务端消息",Toast.LENGTH_SHORT).show() } Log.e(TAG, "onCreate:devices 由远程特征通知触发的回调 ") // 在发现服务回调这里,我们需要对我们在服务端设置的读特征值进行通知设置; // 这样当服务端写入了数据,就会回调我们的onCharacteristicChanged()拿到数据 val data2 = String(characteristic!!.value) Log.e( TAG, "onCharacteristicChanged 接收到了数据 $data2" ) } override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray ) { super.onCharacteristicChanged(gatt, characteristic, value) Log.e(TAG, "onCreate:devices 由远程特征通知触发的回调 ") } }) //向服务端发送数据 要找到 WRITE的特征 写入数据 而服务端会触发 fun send(){ // 找到服务 val service = bluetoothGatt!!.getService(UUID_SERVER) // 拿到写的特征值 - 特征值 写入数据 // 客户端就是调用写,服务端就是调用读 相当于通道 val characteristic = service!!.getCharacteristic(UUID_CHAR_WRITE) bluetoothGatt!!.setCharacteristicNotification(characteristic, true) characteristic.value = "6666".toByteArray() bluetoothGatt!!.writeCharacteristic(characteristic) Log.e(TAG, "sendData 发送数据成功") }