Bluetooth 蓝牙
源:蓝牙概览
Android 平台支持蓝牙网络堆栈,允许设备与其他蓝牙设备进行无线数据交换。应用框架提供了通过 Android Bluetooth API 访问蓝牙功能的途径。
1. 权限声明
从 Android 12 (API 31) 开始,蓝牙权限模型发生了重大变化。
Android 12 及以上 (API 31+)
如果你的应用目标是 Android 12 或更高版本,需要在 AndroidManifest.xml 中声明以下权限:
xml
<!-- 如果应用寻找蓝牙设备(如 BLE 外围设备) -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<!-- 如果应用让当前设备可被其他设备发现 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- 如果应用与已配对的蓝牙设备通信 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />注意: neverForLocation 标志表示你使用蓝牙扫描不是为了推断用户位置。如果你确实需要通过蓝牙推断位置,则不能添加此标志,并且需要申请定位权限。
Android 11 及以下 (API 30-)
xml
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- 需要定位权限才能进行蓝牙扫描 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />2. 设置蓝牙
在使用蓝牙之前,需要验证设备是否支持蓝牙,并确保蓝牙已启用。
kotlin
val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java)
val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter
if (bluetoothAdapter == null) {
// 设备不支持蓝牙
}
if (bluetoothAdapter?.isEnabled == false) {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
// 启动 Activity 请求用户开启蓝牙 (需要处理 Activity Result)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}3. 查找设备 (BLE 扫描)
使用 BluetoothLeScanner 扫描 BLE 设备。
kotlin
private val leScanner = bluetoothAdapter.bluetoothLeScanner
private var scanning = false
private val handler = Handler(Looper.getMainLooper())
// 10秒后停止扫描
private val SCAN_PERIOD: Long = 10000
private fun scanLeDevice() {
if (!scanning) {
// 停止扫描的 Runnable
handler.postDelayed({
scanning = false
leScanner.stopScan(leScanCallback)
}, SCAN_PERIOD)
scanning = true
leScanner.startScan(leScanCallback)
} else {
scanning = false
leScanner.stopScan(leScanCallback)
}
}
// 扫描回调
private val leScanCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
val device = result.device
// 发现设备,可以在这里更新 UI
Log.d("BLE", "Found device: ${device.name} - ${device.address}")
}
}4. 连接到 GATT 服务端
连接到 BLE 设备主要涉及连接到该设备上的 GATT 服务器。
kotlin
var bluetoothGatt: BluetoothGatt? = null
private fun connectToDevice(device: BluetoothDevice) {
// autoConnect = false 表示立即连接
bluetoothGatt = device.connectGatt(context, false, gattCallback)
}
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
Log.i("BLE", "Connected to GATT server.")
// 连接成功后,开始发现服务
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
Log.i("BLE", "Disconnected from GATT server.")
}
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 服务已发现,可以遍历 gatt.services
displayGattServices(gatt.services)
} else {
Log.w("BLE", "onServicesDiscovered received: $status")
}
}
// ... 其他回调,如 onCharacteristicRead, onCharacteristicWrite
}5. 传输数据
读取特征 (Characteristic)
kotlin
fun readCharacteristic(serviceUUID: UUID, charUUID: UUID) {
val service = bluetoothGatt?.getService(serviceUUID)
val characteristic = service?.getCharacteristic(charUUID)
if (characteristic != null) {
bluetoothGatt?.readCharacteristic(characteristic)
}
}
// 结果在 gattCallback.onCharacteristicRead 中返回写入特征
kotlin
fun writeCharacteristic(serviceUUID: UUID, charUUID: UUID, value: ByteArray) {
val service = bluetoothGatt?.getService(serviceUUID)
val characteristic = service?.getCharacteristic(charUUID)
if (characteristic != null) {
// 根据 API 版本设置写入类型和值
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
bluetoothGatt?.writeCharacteristic(characteristic, value, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
} else {
characteristic.value = value
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
bluetoothGatt?.writeCharacteristic(characteristic)
}
}
}订阅通知 (Notifications)
kotlin
fun setCharacteristicNotification(characteristic: BluetoothGattCharacteristic, enabled: Boolean) {
bluetoothGatt?.setCharacteristicNotification(characteristic, enabled)
// 还需要写入 Descriptor 才能真正开启通知
val uuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") // Client Characteristic Configuration Descriptor
val descriptor = characteristic.getDescriptor(uuid)
val value = if (enabled) BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE else BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
bluetoothGatt?.writeDescriptor(descriptor, value)
} else {
descriptor.value = value
bluetoothGatt?.writeDescriptor(descriptor)
}
}
// 数据更新在 onCharacteristicChanged 回调中6. 最佳实践
- 权限处理:务必适配 Android 12+ 的新权限模型,并在运行时请求。
- 不在主线程操作:所有的蓝牙操作(扫描、连接、读写)都应当放在后台线程执行,虽然回调通常在 Binder 线程,但处理逻辑不应阻塞 UI。
- 及时停止扫描:扫描非常耗电,找到设备后应立即停止扫描。
- GATT 操作顺序:GATT 操作(读、写)是串行的。如果连续发起多个操作,可能会失败。最好建立一个命令队列。
- 资源释放:不再使用时调用
bluetoothGatt?.close()释放资源。