1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
| 一、前言
在物联网设备开发中,蓝牙通信(Bluetooth Low Energy, BLE) 是最常见的无线数据传输方式之一。 很多 Android 项目(如健康手环、传感模块等)都需要实现 BLE 的扫描、连接、收发数据、断线重连等功能。
本文将分享一个我在项目中实际使用的 BLE 工具类 —— BluetoothBleUtil, 它将 Android 原生的蓝牙 API 封装成简单易用的接口,让开发者可以几行代码就完成 BLE 通信。
二、功能概述
本工具类实现了以下 BLE 功能模块:
功能 说明 蓝牙初始化 初始化 BluetoothAdapter、BluetoothManager 扫描设备 支持超时自动停止扫描 自动连接目标设备 根据设备名过滤并自动连接 读写 GATT 特征 支持发送指令与接收通知 断开与释放 断线自动清理,防止内存泄漏 重连功能 自动重连上次已连接设备 状态监听 支持 LiveData 与回调接口 三、核心架构设计
BluetoothBleUtil 使用 Kotlin 单例模式 (object) 设计,保证 BLE 连接全局唯一。 核心流程如下:
startScan() → onScanResult() → connect() → onServicesDiscovered() ↓ ↓ stopScan() enableNotify() ↓ ↓ sendBytes() ←→ onCharacteristicChanged()
四、代码实现详解
import android.annotation.SuppressLint import android.bluetooth.* import android.bluetooth.le.* import android.content.Context import android.os.Build import android.os.Handler import android.os.Looper import android.util.Log import androidx.lifecycle.MutableLiveData import com.comm.library.utils.LogUtils import com.comm.library.utils.SPUtils import com.htnova.gasdetection.model.SpConstance import java.util.*
/** * 低功耗蓝牙 */ @SuppressLint("MissingPermission") object BluetoothBleUtil {
private const val TAG = "gasdetection"
// ------------------------- BLE参数配置 ------------------------- private const val DEVICE_NAME = "GCPID" private val SERVICE_UUID = UUID.fromString("0000a002-0000-1000-8000-00805f9b34fb") private val WRITE_UUID = UUID.fromString("0000c303-0000-1000-8000-00805f9b34fb") private val NOTIFY_UUID = UUID.fromString("0000c305-0000-1000-8000-00805f9b34fb")
// ------------------------- 回调接口 ------------------------- interface Callback { fun onScanStarted() {} fun onDeviceFound(device: BluetoothDevice) {} fun onScanFinished() {} fun onConnected(device: BluetoothDevice) {} fun onDisconnected(device: BluetoothDevice) {} fun onMessageReceived(device: BluetoothDevice, data: ByteArray) {} fun onError(msg: String) {} }
// ------------------------- 内部状态 ------------------------- private var callback: Callback? = null private lateinit var appContext: Context private var adapter: BluetoothAdapter? = null private var scanner: BluetoothLeScanner? = null private var bluetoothGatt: BluetoothGatt? = null private var writeCharacteristic: BluetoothGattCharacteristic? = null private var notifyCharacteristic: BluetoothGattCharacteristic? = null private val handler = Handler(Looper.getMainLooper())
private var isScanning = false private var isConnected = false
val connectedDeviceLiveData = MutableLiveData<BluetoothDevice?>()
// ------------------------- 初始化 ------------------------- fun init(context: Context, cb: Callback) { appContext = context.applicationContext callback = cb adapter = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter scanner = adapter?.bluetoothLeScanner }
// ------------------------- 扫描 ------------------------- fun startScan(timeoutMs: Long = 8000) { if (isScanning) return if (adapter == null || !adapter!!.isEnabled) { callback?.onError("蓝牙未开启") return }
// val filters = listOf(ScanFilter.Builder().setDeviceName(DEVICE_NAME).build()) val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
isScanning = true callback?.onScanStarted() scanner?.startScan(null, settings, scanCallback)
handler.postDelayed({ stopScan() }, timeoutMs) }
fun stopScan() { if (!isScanning) return isScanning = false try { scanner?.stopScan(scanCallback) } catch (_: Exception) {} callback?.onScanFinished() }
private val scanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { val device = result.device callback?.onDeviceFound(device) if (device.name == DEVICE_NAME) { callback?.onDeviceFound(device) stopScan() connect(device) } }
override fun onScanFailed(errorCode: Int) { callback?.onError("扫描失败: $errorCode") isScanning = false } }
// ------------------------- 连接 ------------------------- fun connect(device: BluetoothDevice) { Log.d(TAG, "正在连接: ${device.name} ${device.address}") SPUtils.putString(SpConstance.BLUE_ADDRESS,device.address) bluetoothGatt = device.connectGatt(appContext, false, gattCallback) }
fun disconnect() { isConnected = false try { bluetoothGatt?.disconnect() } catch (_: Exception) {} try { bluetoothGatt?.close() } catch (_: Exception) {} bluetoothGatt = null connectedDeviceLiveData.postValue(null) }
// ------------------------- GATT回调 ------------------------- private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { when (newState) { BluetoothProfile.STATE_CONNECTED -> { isConnected = true connectedDeviceLiveData.postValue(gatt.device) callback?.onConnected(gatt.device) Log.d(TAG, "已连接,开始发现服务") gatt.discoverServices() } BluetoothProfile.STATE_DISCONNECTED -> { isConnected = false connectedDeviceLiveData.postValue(null) callback?.onDisconnected(gatt.device) Log.d(TAG, "已断开: ${gatt.device.name}") // try { bluetoothGatt?.close() } catch (_: Exception) {} // bluetoothGatt = null // connectedDeviceLiveData.postValue(null) } } }
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { if (status == BluetoothGatt.GATT_SUCCESS) { val service = gatt.getService(SERVICE_UUID) if (service != null) { writeCharacteristic = service.getCharacteristic(WRITE_UUID) notifyCharacteristic = service.getCharacteristic(NOTIFY_UUID)
// 启用通知 //注意:BLE 特征通知的描述符(Descriptor)一般是 Client Characteristic Configuration Descriptor (CCCD),UUID 固定为: //00002902-0000-1000-8000-00805f9b34fb //这是 BLE 规范规定的,用来启用特征通知或指示。 if (notifyCharacteristic != null) { gatt.setCharacteristicNotification(notifyCharacteristic, true) val descriptor = notifyCharacteristic!!.getDescriptor( UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") ) descriptor?.let { it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(it) } } } else { callback?.onError("未发现指定服务UUID") } } }
override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { if (characteristic.uuid == NOTIFY_UUID) { val data = characteristic.value callback?.onMessageReceived(gatt.device, data) } }
override fun onCharacteristicWrite( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int ) { if (status != BluetoothGatt.GATT_SUCCESS) { callback?.onError("写入失败: $status") } } }
// ------------------------- 写数据 ------------------------- fun sendBytes(data: ByteArray) { if (!isConnected || writeCharacteristic == null || bluetoothGatt == null) { callback?.onError("未连接设备") return } writeCharacteristic!!.value = data val result = bluetoothGatt!!.writeCharacteristic(writeCharacteristic) if (!result) { callback?.onError("发送失败: writeCharacteristic返回false") } }
fun sendMessage(msg: String) = sendBytes(msg.toByteArray())
// ------------------------- 工具 ------------------------- fun isBluetoothEnabled(): Boolean = adapter?.isEnabled == true fun enableBluetooth() = adapter?.enable() fun disableBluetooth() = adapter?.disable()
fun release() { stopScan() disconnect() callback = null adapter = null scanner = null }
fun getConnectedBleDevices(): List<BluetoothDevice> { val manager = appContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager return manager.getConnectedDevices(BluetoothProfile.GATT) }
//连接上次连接的 fun tryReconnectLastDevice() { val address = SPUtils.getString(SpConstance.BLUE_ADDRESS,"") ?: return val device = adapter?.getRemoteDevice(address) if (device != null) { Log.d(TAG, "尝试重连上次设备: ${device.address}") // connect(device) bluetoothGatt?.connect() } } }
五、权限配置
Android 12+ 必须在 AndroidManifest.xml 中声明以下权限:
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
同时,在运行时动态申请:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { requestPermissions(arrayOf( Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN ), 100) }
|