BluetoothGattとは何か、メソッド、AndroidでのGATT BLEプロトコル

著者: IT Sectr 公開日: 2026-07-16 読了時間: 10 分

BluetoothGatt — BLE接続上でGATT(Generic Attribute Profile)クライアント操作のためのAPIを提供するAndroidクラスです。BluetoothGattはリモートGATTサーバー(周辺BLEデバイス)への接続をカプセル化し、サービス検出、特性の読み書き、通知と表示の購読など、すべてのプロファイル操作を管理します。BluetoothGattインスタンスはBluetoothDevice.connectGatt()とBluetoothGattCallbackを介して取得されます。Android Developers、2026年によると、BluetoothGattは双方向BLE通信の中心的なクラスであり、BLE 4.0からBLE 5.4までのGATT操作をサポートしています。

重要ポイント

  • BluetoothGatt — 周辺デバイスとのBLE接続を管理するGATTクライアント用Androidクラス
  • connectGatt() — BluetoothGattを作成するBluetoothDeviceメソッド。コンテキスト、autoConnect、コールバック、トランスポートを受け取ります
  • discoverServices() — GATT階層を取得するメソッド:サービス(BluetoothGattService)、特性(BluetoothGattCharacteristic)
  • readCharacteristic/writeCharacteristic — BluetoothGattCallbackを介して非同期結果を得る読み書きメソッド
  • setCharacteristicNotification — CCCD記述子の必須書き込みを伴うBLE通知購読メソッド

BluetoothGattとは:本質と接続作成

BluetoothGatt — Androidデバイス(セントラル)とBLE周辺機器(サーバー)間のGATT接続を表すプロキシオブジェクトです。各BluetoothGattインスタンスは1つのアクティブなBLE接続に対応します。すべてのGATTプロファイル操作(検出、読み取り、書き込み、通知)はこれを介して実行されます。BluetoothGattは直接作成されるのではなく、BluetoothDevice.connectGatt()メソッドによって返されます。

BluetoothGattの作成には4つのパラメータが必要です。Context — アプリケーションコンテキスト(ActivityまたはApplication)。autoConnect — falseの場合、Androidはすぐに直接接続を開始します。trueの場合、Androidはデバイスが検出されたときに自動的に接続します(バックグラウンド接続に便利)。BluetoothGattCallback — すべてのGATTイベントの必須コールバック。transport — BluetoothDevice.TRANSPORT_LE(BLE)またはTRANSPORT_BREDR(Classic)。BLEデバイスでは常にTRANSPORT_LEを使用してください。

BluetoothGattのライフサイクルは5つの状態で構成されます。DISCONNECTED — 初期状態。CONNECTING — connectGatt呼び出し後、確認前。CONNECTED — STATE_CONNECTEDでonConnectionStateChange後。接続後、GATT階層を取得するためにdiscoverServices()が呼び出されます。作業終了後 — 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パラメータは直接接続(スキャンされたデバイス用)を意味します。STATE_CONNECTEDでのonConnectionStateChange時に、すぐに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("書き込み")
            if (and(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) props.add("応答なし書き込み")
            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() — リモートBLEデバイスから特性値を読み取るための非同期BluetoothGattメソッドです。結果はBluetoothGattCallbackのonCharacteristicRead()に届きます。デバイスに2つ以上の一致する特性がある場合(可能性は低いですが)、readCharacteristic()は対象外の特性を読み取る可能性があります — UUIDではなくBluetoothGattCharacteristicインスタンスでreadCharacteristic()を呼び出す方が安全です。

readDescriptor() — 特性記述子の値を読み取るメソッドです。典型的な記述子はCCCD(Client Characteristic Configuration Descriptor、UUID 0x2902)で、通知が有効かどうかを決定します。結果はonDescriptorRead()に届きます。実際には記述子を読み取る必要はほとんどありません — CCCDはsetCharacteristicNotification()で管理されますが、カスタム記述子(User Description 0x2901、Presentation Format 0x2904)の場合、readDescriptor()がメタデータを取得する唯一の方法です。

MTUと大規模データの読み取り — 特性値がMTU(BLE 4.0では23バイト)を超える場合、AndroidはBLEスタックを介した読み取り要求のシーケンスによって自動的にデータを分割および再構成します。拡張MTU(最大251バイト)を持つBLE 5.0+の場合、断片化は不要です — 1回の読み取りで完全なデータが返されます。読み取り前に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心拍数:bytes = flags、second = 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(Heart Rate Measurement)が含まれています — Bluetooth SIGの標準BLEプロファイルです。読み取り前に、hasPropertyを介してPROPERTY_READプロパティがチェックされます。parseHeartRateはBLE心拍数形式を解析します:最初のバイト — flags(データ形式)、2番目 — bpm値。CCCD記述子(0x2902)は通知ステータスを確認するために読み取られます。

特性の書き込み:WriteTypeを使用したwriteCharacteristic

writeCharacteristic() — BLE周辺機器にデータを書き込むBluetoothGattメソッドです。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デバイス自体で通知を有効にするには、CCCD記述子(0x2902)に値0x0100も書き込む必要があります。

CCCD(Client Characteristic Configuration Descriptor) — BLE周辺機器からの通知送信を制御する記述子です。値0x0000 — 通知無効、0x0100 — 通知有効、0x0200 — 表示有効。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通知購読の正しい2段階プロトコルを実装しています。enableNotificationは最初にAndroidでsetCharacteristicNotification(true)を呼び出し、次にwriteDescriptorを介してCCCD記述子に0x0100を書き込みます。disableNotificationは逆の操作を実行します。CCCDの書き込みがないと、BLEデバイスは通知を送信しません — これはAndroidのBLE開発者にとって最も一般的なエラーです。

BluetoothGattCallbackを使用したKotlinでの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
    }
}

GATTクライアントGattClientはコルーチン(suspendCoroutine)を使用して、コールバックベースのBluetoothGatt APIを順次呼び出しに変換します。connect()はonServicesDiscoveredを待機し、その後GATT階層が利用可能になります。readCharacteristicValue()はonCharacteristicReadを待機します。このアプローチによりコールバックのネストがなくなり、BLEコードが線形になります。release()はリソースのクリーンアップを保証します — ActivityのonDestroyまたはViewModel.onClearedでの必須呼び出しです。

よくある質問

AndroidでのBluetoothGattとは何ですか?

BluetoothGatt — 周辺デバイスとのBLE接続を管理するGATTクライアント用のAndroidクラスです。BluetoothDevice.connectGatt()を介して作成され、discoverServices()、readCharacteristic()、writeCharacteristic()、setCharacteristicNotification()メソッドを提供します。すべての操作の結果はBluetoothGattCallbackを介して非同期に届きます。BluetoothGattなしでは、Androidでの双方向BLE通信は不可能です。

onServicesDiscoveredがステータス133を返すのはなぜですか?

ステータス133(GATT_ERROR)はAndroid BLEスタックの内部エラーを示します。原因:検出中にデバイスが切断された、MTUが最小値(23バイト)未満、またはBLEスタックが過負荷。解決策:500msの遅延でdiscoverServices()を再試行し、デバイスのRSSIを確認し、周辺機器が現在の状態でGATT検出をサポートしていることを確認してください。

確認付きで特性を正しく書き込むには?

確認付きで書き込むには、WRITE_TYPE_DEFAULT(API 33+:BluetoothGattCharacteristicWriteRequest)でwriteCharacteristic()を呼び出します。成功すると、BLEデバイスが確認を送信し、AndroidがGATT_SUCCESSでonCharacteristicWriteを呼び出します。デバイスが30秒以内(スタックタイムアウト)に応答しない場合、コールバックはエラーステータスを返します。ウォッチドッグには、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 — BLE接続用のAndroid GATTクライアント。BluetoothDevice.connectGatt()とBluetoothGattCallbackを介して作成
  • discoverServices() — BLEデバイスのサービス、特性、記述子を取得するための接続後の必須ステップ
  • 読み取り — onCharacteristicRead()で非同期結果を得るreadCharacteristic()。大規模データにはMTUネゴシエーションが必要
  • 書き込み — WriteTypeを使用したwriteCharacteristic():DEFAULT(withResponse)またはNO_RESPONSE(確認なし)
  • 通知 — 2段階アクティベーション:setCharacteristicNotification() + CCCD(0x2902)への値0x0100の書き込み
  • API 33+ — リクエストオブジェクトを使用した新しいメソッドwriteCharacteristic(request)とreadCharacteristic(request)
  • リソース解放 — BLE接続リークを防ぐためのdisconnect()とclose()の必須呼び出し

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください