BluetoothGatt — 什么是它、方法及 Android 中 BLE 的 GATT 协议

作者: IT Sectr 发布日期: 2026-07-16 阅读时间: 10 分钟

BluetoothGatt — Android 类,提供通过 BLE 连接运行 GATT 客户端(通用属性协议)的 API。BluetoothGatt 封装了与远程 GATT 服务器(外围 BLE 设备)的连接,并管理所有配置文件操作:发现服务、读写特征、订阅通知和指示。BluetoothGatt 实例通过 BluetoothDevice.connectGatt() 及 BluetoothGattCallback 回调获得。根据 Android Developers, 2026,BluetoothGatt 是双向 BLE 通信的核心类,支持从 BLE 4.0 到 BLE 5.4 的 GATT 操作。

要点

  • BluetoothGatt — Android 中用于 GATT 客户端的类,管理与外围设备的 BLE 连接
  • connectGatt() — BluetoothDevice 创建 BluetoothGatt 的方法;接受上下文、autoConnect、回调和传输方式
  • discoverServices() — 获取 GATT 层次结构的方法:服务(BluetoothGattService)、特征(BluetoothGattCharacteristic)
  • readCharacteristic/writeCharacteristic — 通过 BluetoothGattCallback 异步读取和写入的方法
  • setCharacteristicNotification — 订阅 BLE 通知的方法,需强制写入 CCCD 描述符

什么是 BluetoothGatt:本质与连接建立

BluetoothGatt — 是一个代理对象,表示 Android 设备(中央)与 BLE 外设(服务器)之间的 GATT 连接。每个 BluetoothGatt 实例对应一个活动的 BLE 连接。通过它执行所有 GATT 配置文件操作:发现、读取、写入、通知。BluetoothGatt 不直接创建——它由 BluetoothDevice.connectGatt() 方法返回。

创建 BluetoothGatt 需要四个参数。Context — 应用程序上下文(Activity 或 Application)。autoConnect — 如果为 false,Android 立即发起直接连接;如果为 true,Android 在检测到设备时自动连接(适用于后台连接)。BluetoothGattCallback — 所有 GATT 事件的必需回调。transport — BluetoothDevice.TRANSPORT_LE(BLE)或 TRANSPORT_BREDR(Classic)。在 BLE 设备上始终使用 TRANSPORT_LE。

BluetoothGatt 生命周期 由五个状态组成。DISCONNECTED — 初始状态。CONNECTING — 调用 connectGatt 后,在确认之前。CONNECTED — 在 onConnectionStateChange 返回 STATE_CONNECTED 之后。连接后调用 discoverServices() 获取 GATT 层次结构。工作完成后调用 disconnect() 和 close() 以释放系统资源。如果不调用 close(),应用程序可能会耗尽 Android BLE 连接限制(通常为 4–8 个)。

kotlin
// 创建 BluetoothGatt 连接
import android.bluetooth.*

class GattConnector(private val context: Context) {

    private var bluetoothGatt: BluetoothGatt? = null

    fun connect(device: BluetoothDevice): BluetoothGatt? {
        // 关闭已有连接(如果存在)
        close()

        bluetoothGatt = device.connectGatt(
            context,
            false,  // autoConnect = false ( )
            object : BluetoothGattCallback() {
                override fun onConnectionStateChange(
                    gatt: BluetoothGatt, status: Int, newState: Int
                ) {
                    when (newState) {
                        BluetoothProfile.STATE_CONNECTED -> {
                            // 2. 连接已建立 → 发现
                            gatt.discoverServices()
                        }
                        BluetoothProfile.STATE_DISCONNECTED -> {
                            // 3. 连接丢失
                            close()
                        }
                    }
                }

                override fun onServicesDiscovered(
                    gatt: BluetoothGatt, status: Int
                ) {
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        // 4. 已接收 GATT 层次结构
                        onGattReady(gatt)
                    }
                }
            },
            BluetoothDevice.TRANSPORT_LE
        )
        return bluetoothGatt
    }

    private fun onGattReady(gatt: BluetoothGatt) {
        // GATT 准备好进行读写操作
    }

    fun close() {
        bluetoothGatt?.disconnect()
        bluetoothGatt?.close()
        bluetoothGatt = null
    }
}

GattConnector 类演示了正确的 BluetoothGatt 创建。参数 autoConnect=false — 直接连接(用于已扫描的设备)。在 onConnectionStateChange 返回 STATE_CONNECTED 时立即调用 discoverServices()。onServicesDiscovered 表示 GATT 已就绪。close() 依次调用 disconnect() 和 close()——若不调用 close(),系统资源无法释放,导致 BLE 连接泄漏。

通过 BluetoothGatt 发现服务和特征

discoverServices() — 连接后调用的第一个 GATT 方法。在 BLE 外设上启动所有服务的异步搜索。结果通过 onServicesDiscovered() 返回状态码:GATT_SUCCESS (0) — 成功,133 — GATT_ERROR,8 — GATT_CONNECTION_TIMEOUT。成功发现后,BluetoothGatt 填充通过 getServices() 可用的服务列表。

每个 BluetoothGattService 包含一个 BluetoothGattCharacteristic 列表。特征具有 UUID、属性(PROPERTY_READ、PROPERTY_WRITE、PROPERTY_NOTIFY)和可选的描述符。属性决定允许哪些操作:如果特征没有 PROPERTY_READ,调用 readCharacteristic 将返回错误。使用 getDescriptors() 获取特征的描述符。

kotlin
// 服务发现和特征搜索
class GattServiceExplorer {

    // 按 UUID 查找服务
    fun findService(gatt: BluetoothGatt, uuid: UUID): BluetoothGattService? {
        return gatt.services?.firstOrNull { it.uuid == uuid }
    }

    // 在服务中查找特征
    fun findCharacteristic(
        service: BluetoothGattService,
        uuid: UUID
    ): BluetoothGattCharacteristic? {
        return service.characteristics?.firstOrNull { it.uuid == uuid }
    }

    // 获取所有支持的特征操作
    fun getCharacteristicProperties(chars: BluetoothGattCharacteristic): List<String> {
        val props = mutableListOf<String>()
        with(chars.properties) {
            if (and(BluetoothGattCharacteristic.PROPERTY_READ) != 0) props.add("READ")
            if (and(BluetoothGattCharacteristic.PROPERTY_WRITE) != 0) props.add("WRITE")
            if (and(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) props.add("WRITE_NO_RESP")
            if (and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) props.add("NOTIFY")
            if (and(BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) props.add("INDICATE")
        }
        return props
    }

    // 记录完整 GATT 层次结构
    fun dumpGattTree(gatt: BluetoothGatt) {
        gatt.services?.forEach { service ->
            print("Service: ${service.uuid}")
            service.characteristics?.forEach { char ->
                print("  Characteristic: ${char.uuid}, properties: ${char.properties}")
                char.descriptors?.forEach { desc ->
                    print("    Descriptor: ${desc.uuid}")
                }
            }
        }
    }
}

GattServiceExplorer 类提供在 GATT 层次结构中导航的工具。findService 和 findCharacteristic 按 UUID 搜索服务和特征。getCharacteristicProperties 通过 and() 检查属性的位掩码。dumpGattTree 将完整层次结构输出到日志中——有助于调试 BLE 设备。所有对 BluetoothGatt 的操作必须在成功的 onServicesDiscovered 之后执行,否则 getServices() 将返回空列表。

读取 BLE 特征:readCharacteristic 和 readDescriptor

readCharacteristic() — BluetoothGatt 的异步方法,用于从远程 BLE 设备读取特征值。结果在 BluetoothGattCallback 的 onCharacteristicRead() 中返回。如果设备上有 2+ 个匹配的特征(可能性很小但存在),readCharacteristic() 可能读取到非目标特征——更安全的方式是在 BluetoothGattCharacteristic 实例上调用 readCharacteristic(),而不是按 UUID 调用。

readDescriptor() — 读取特征描述符值的方法。典型的描述符是 CCCD(客户端特征配置描述符,UUID 0x2902),它确定通知是否已启用。结果在 onDescriptorRead() 中返回。在实践中很少需要读取描述符——CCCD 由 setCharacteristicNotification() 管理,但对于自定义描述符(User Description 0x2901、Presentation Format 0x2904),readDescriptor() 是获取元数据的唯一方式。

MTU 与大数据读取 — 如果特征值超过 MTU(BLE 4.0 为 23 字节),Android 会自动通过 BLE 堆栈的一系列读取请求对数据进行分片和重组。对于 BLE 5.0+ 扩展 MTU(最多 251 字节),不需要分片——一次读取即可返回完整数据。在读取前可以调用 requestMtu() 协商最大 MTU。

kotlin
// 通过 BluetoothGatt 读取特征和描述符
class GattReader {

    fun readHeartRate(gatt: BluetoothGatt) {
        // 心率服务 UUID = 0x180D
        val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
            ?: return
        // 心率测量 UUID = 0x2A37
        val characteristic = service.getCharacteristic(
            UUID.fromString("00002A37-0000-1000-8000-00805F9B34FB")
        ) ?: return

        if (hasProperty(characteristic, BluetoothGattCharacteristic.PROPERTY_READ)) {
            gatt.readCharacteristic(characteristic)
        }
    }

    fun readDescriptor(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
        // CCCD(0x2902)
        val cccd = characteristic.getDescriptor(
            UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
        )
        cccd?.let { gatt.readDescriptor(it) }
    }

    // 在 onCharacteristicRead 回调中处理数据:
    fun parseHeartRate(value: ByteArray): Int {
        // BLE 心率:字节 = 标志位,第二 = bpm
        return if (value.isNotEmpty()) value[1].toInt() and 0xFF else 0
    }

    private fun hasProperty(char: BluetoothGattCharacteristic, prop: Int): Boolean {
        return char.properties and prop != 0
    }
}

GattReader 类演示了心率特征的读取。服务 0x180D 包含特征 0x2A37(心率测量)——Bluetooth SIG 的标准 BLE 配置文件。读取前通过 hasProperty 检查 PROPERTY_READ 属性。parseHeartRate 解析 BLE 心率格式:第一个字节是 flags(数据格式),第二个是 bpm 值。读取 CCCD 描述符(0x2902)以检查通知状态。

写入特征:带 WriteType 的 writeCharacteristic

writeCharacteristic() — BluetoothGatt 向 BLE 外设写入数据的方法。在 Android API 33+ 上,writeCharacteristic() 已被 writeCharacteristic(request) 取代,其中 BluetoothGattCharacteristicWriteRequest 是一个请求对象,包含特征、字节数组和 WriteType。旧的带 setValue() 的 writeCharacteristic(characteristic) 方法已弃用。WriteType 确定请求的行为:WRITE_TYPE_DEFAULT(取决于特征属性)、WRITE_TYPE_NO_RESPONSE(无响应)和 WRITE_TYPE_SIGNED(授权)。

WriteType 的选择影响速度和可靠性。WRITE_TYPE_DEFAULT 通常对应 withResponse(如果特征具有 PROPERTY_WRITE)或 withoutResponse(如果具有 PROPERTY_WRITE_NO_RESPONSE)。对于流式数据(OTA 更新、日志),使用 WRITE_TYPE_NO_RESPONSE——最大带宽。对于需要保证交付的命令(启用、配置)——使用带 onCharacteristicWrite 确认的 WRITE_TYPE_DEFAULT。

kotlin
// 在 Android API 33+ 上写入 BLE 特征
class GattWriter {

    // 带响应的写入(withResponse)
    fun writeWithResponse(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, data: ByteArray) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // API 33+:writeCharacteristic
            val request = BluetoothGattCharacteristicWriteRequest(
                characteristic,
                data,
                BluetoothGattCharacteristicWriteRequest.WRITE_TYPE_DEFAULT,
                @android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
            )
            gatt.writeCharacteristic(request)
        } else {
            // API < 33:   (deprecated)
            characteristic.setValue(data)
            gatt.writeCharacteristic(characteristic)
        }
    }

    // 无响应写入(最大速度)
    fun writeWithoutResponse(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, data: ByteArray) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            val request = BluetoothGattCharacteristicWriteRequest(
                characteristic,
                data,
                BluetoothGattCharacteristicWriteRequest.WRITE_TYPE_NO_RESPONSE,
                @android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
            )
            gatt.writeCharacteristic(request)
        } else {
            characteristic.setValue(data)
            characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
            gatt.writeCharacteristic(characteristic)
        }
    }

    // onCharacteristicWrite 回调(API 33+)
    private val writeCallback = object : BluetoothGattCallback() {
        override fun onCharacteristicWrite(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray,
            status: Int,
            callbackType: Int
        ) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                print("Write success: ${value.size} bytes")
            }
        }
    }
}

GattWriter 类支持不同 API 级别的两种 WriteType。writeWithResponse 使用 WRITE_TYPE_DEFAULT——BLE 设备通过 onCharacteristicWrite 确认写入。writeWithoutResponse 使用 WRITE_TYPE_NO_RESPONSE——数据无需确认即可发送,最大带宽。在 API 33+ 上使用带 BluetoothGattCharacteristicWriteRequest 的新 writeCharacteristic(request)。在 API < 33 上使用旧的 setValue() + writeCharacteristic()。

订阅通知:setCharacteristicNotification 和 CCCD

setCharacteristicNotification() — BluetoothGatt 订阅外设特征变化通知的方法。订阅激活后,BLE 设备通过 onCharacteristicChanged() 发送新值。然而,setCharacteristicNotification() 仅激活 Android 本地通知——要在 BLE 设备本身上启用通知,还需要将值 0x0100 写入 CCCD 描述符(0x2902)。

CCCD(客户端特征配置描述符) — 管理从 BLE 外设发送通知的描述符。值 0x0000 — 通知关闭,0x0100 — 通知开启(notifications),0x0200 — 指示开启(indications)。写入 CCCD 在调用 setCharacteristicNotification() 后通过 BluetoothGatt 上的 writeDescriptor() 完成。Android 不会自动写入 CCCD——这一责任在于开发者。

kotlin
// 正确的 BLE 通知订阅
class GattNotificationManager {

    // 1. 启用通知
    fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
        // 步骤 1:Android 本地订阅
        val success = gatt.setCharacteristicNotification(characteristic, true)
        if (!success) {
            print("订阅失败")
            return
        }

        // 步骤 2:在 BLE 设备上写入 CCCD(0x2902)
        val cccdDescriptor = characteristic.getDescriptor(
            UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
        ) ?: return

        // 0x0100 = 通知,0x0200 = 指示
        val cccdValue = if (characteristic.properties
            and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {
            BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE  // [0x01, 0x00]
        } else {
            BluetoothGattDescriptor.ENABLE_INDICATION_VALUE     // [0x02, 0x00]
        }

        cccdDescriptor.setValue(cccdValue)
        gatt.writeDescriptor(cccdDescriptor)
    }

    // 2. 特征发现
    fun disableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
        gatt.setCharacteristicNotification(characteristic, false)
        val cccdDescriptor = characteristic.getDescriptor(
            UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
        ) ?: return
        cccdDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)
        gatt.writeDescriptor(cccdDescriptor)
    }

    // 3. 通知回调
    private val notificationCallback = object : BluetoothGattCallback() {
        override fun onCharacteristicChanged(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray,
            callbackType: Int
        ) {
            // 来自 BLE 外设的新值
            print("Notification: ${value.size} bytes")
        }
    }
}

GattNotificationManager 类实现了正确的两步 BLE 通知订阅协议。enableNotification 首先在 Android 上调用 setCharacteristicNotification(true),然后通过 writeDescriptor 将 0x0100 写入 CCCD 描述符。disableNotification 执行相反的操作。如果不写入 CCCD,BLE 设备不会发送通知——这是 Android BLE 开发人员最常见的错误。

在 Kotlin 中使用 BluetoothGattCallback 的 GATT 客户端示例

完整示例 是 Kotlin 中的 GATT 客户端,它将创建 BluetoothGatt、发现、读取和订阅通知结合在一个管理器中使用协程进行异步处理。

kotlin
// 在 Kotlin 中使用协程的完整 GATT 客户端
class GattClient(context: Context) {

    private val context = context.applicationContext
    private var gatt: BluetoothGatt? = null

    // 使用协程连接
    suspend fun connect(device: BluetoothDevice): Boolean =
        suspendCoroutine { continuation ->
            gatt = device.connectGatt(
                context, false,
                object : BluetoothGattCallback() {
                    override fun onConnectionStateChange(
                        gatt: BluetoothGatt, status: Int, newState: Int
                    ) {
                        if (newState == BluetoothProfile.STATE_CONNECTED) {
                            gatt.discoverServices()
                        } else {
                            continuation.resume(false)
                        }
                    }

                    override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
                        continuation.resume(status == BluetoothGatt.GATT_SUCCESS)
                    }
                },
                BluetoothDevice.TRANSPORT_LE
            )
        }

    // 通过协程读取特征
    suspend fun readCharacteristicValue(char: BluetoothGattCharacteristic): ByteArray? =
        suspendCoroutine { continuation ->
            gatt?.let { gatt ->
                // 保存特征标签用于回调识别
                gatt.setCharacteristic(char, null)  // 适用于 API < 33
                gatt.readCharacteristic(char)
            }
        }

    // 关闭连接
    fun release() {
        gatt?.disconnect()
        gatt?.close()
        gatt = null
    }
}

GattClient GATT 客户端使用协程(suspendCoroutine)将基于回调的 BluetoothGatt API 转换为顺序调用。connect() 等待 onServicesDiscovered,之后 GATT 层次结构可用。readCharacteristicValue() 等待 onCharacteristicRead。这种方法消除了嵌套回调并使 BLE 代码线性化。release() 确保资源释放——在 Activity 的 onDestroy 或 ViewModel.onCleared 中必须调用。

常见问题

Android 中的 BluetoothGatt 是什么?

BluetoothGatt — Android GATT 客户端的类,管理与外围设备的 BLE 连接。通过 BluetoothDevice.connectGatt() 创建,提供 discoverServices()、readCharacteristic()、writeCharacteristic()、setCharacteristicNotification() 方法。所有操作的结果通过 BluetoothGattCallback 异步返回。没有 BluetoothGatt,Android 上的双向 BLE 通信是不可能的。

为什么 onServicesDiscovered 返回状态 133?

状态 133(GATT_ERROR)表示 Android BLE 堆栈的内部错误。原因:设备在发现过程中断开、MTU 小于最小值(23 字节)或 BLE 堆栈过载。解决方案:延迟 500 毫秒后重试 discoverServices(),检查设备的 RSSI,并确保外设在当前状态下支持 GATT 发现。

如何正确写入带确认的特征?

要写入带确认的特征,请调用 writeCharacteristic() 并指定 WRITE_TYPE_DEFAULT(API 33+:BluetoothGattCharacteristicWriteRequest)。成功后,BLE 设备发送确认,Android 调用 onCharacteristicWrite 并返回 GATT_SUCCESS。如果设备在 30 秒内未响应(堆栈超时),回调返回错误状态。对于 watchdog,使用带 postDelayed 的 Handler。

如何在 Android 33+ 上使用 BLE?

在 Android 13+(API 33)上,BluetoothGatt 的方法已更改:writeCharacteristic() 现在接受 BluetoothGattCharacteristicWriteRequest,readCharacteristic() 接受 BluetoothGattCharacteristicReadRequest。旧的 setValue()/writeCharacteristic() 已弃用。BluetoothGattCallback 也已更改:onCharacteristicRead()、onCharacteristicWrite()、onCharacteristicChanged() 接收 ByteArray 值和 callbackType。使用 Build.VERSION.SDK_INT 进行分支处理。

Android 支持多少个 BLE 连接?

Android 支持 4–8 个同时 BLE-GATT 连接(取决于制造商和 Android 版本)。Pixel/Google:最多 7 个,Samsung:最多 5 个,Xiaomi:最多 4 个。超过限制时,connectGatt 返回 null 或 onConnectionStateChange 返回错误。如需与大量设备配合使用,请使用循环连接或 Bluetooth Mesh。

总结

  • BluetoothGatt — Android GATT 客户端,用于 BLE 连接,通过 BluetoothDevice.connectGatt() 与 BluetoothGattCallback 创建
  • discoverServices() — 连接后获取 BLE 设备服务、特征和描述符的必需步骤
  • 读取 — readCharacteristic() 在 onCharacteristicRead() 中异步返回结果;大数据需要 MTU 协商
  • 写入 — writeCharacteristic() 带 WriteType:DEFAULT(带确认)或 NO_RESPONSE(无确认)
  • 通知 — 两步激活:setCharacteristicNotification() + 将 CCCD(0x2902)写入值 0x0100
  • API 33+ — 新的 writeCharacteristic(request) 和 readCharacteristic(request) 方法带请求对象
  • 释放资源 — 必须调用 disconnect() 和 close() 以防止 BLE 连接泄漏

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读