Android低功耗ブルートゥースGatt接続チュートリアル:Kotlinを使用して実装
前書き
以前の仕事で実装した低功耗ブルートゥース接続を復習するのに少し時間をかけました。
忘れてしまうのが心配なので、
再度見直して記録を残したいと思います。
実装が必要な方々にも役立つことを願っています。
Android 12以降では権限関連の処理が追加されましたので、注意してください!
ここでは私の処理方法を紹介しますので、参考にしてください:
最終目標はこのようになります
以前のjetpack composeの練習と連携させて
データを実際のデータに変換し
最終的にgattブルートゥースに接続できるようにします

基本概念
まずは
ブルートゥーススキャンの方法を紹介します
大まかに3種類あります
BluetoothAdapter.startDiscovery() -> クラシックブルートゥースとBLEブルートゥースの両方をスキャン
BluetoothAdapter.startLeScan() -> 低功耗ブルートゥースをスキャンするためのもの ---- 廃止されました
BluetoothLeScanner.startScan() -> 新しいBLEスキャン方法
しかし、API内の注釈を見ると
現在startLeScanは廃止されています
API21で廃止されました
各ブルートゥースデバイスの発見APIを比較してみました
- スキャンプロセスは通常12秒間実行されます
- 非同期呼び出しです
-
異なるステップを実行するためにブロードキャストを登録します。例:
ACTION_DISCOVERY_STARTED -> Discoveryが開始されたとき ACTION_DISCOVERY_FINISHED -> Discoveryが完了したとき BluetoothDevice.ACTION_FOUND -> ブルートゥースデバイスが発見されたとき </a> </li> <li> <a href="javascript:void(0)">ブルートゥースデバイスに接続する際には startDiscovery中であってはいけません 発見を終了するためにcancelDiscovery()を呼び出す必要があります </a> </li> <li> <a href="javascript:void(0)"> DiscoveryはActivityによって管理されていません それはシステムサービスです したがって、万が一に備えてcancelDiscovery()を使用する必要があります Discoveryが実行されていないことを確認し ブルートゥースデバイスに接続する際に デバイスがDiscovery中でないことを確認します </a> </li> <li> <a href="javascript:void(0)">Discoveryは現在発見可能なブルートゥースデバイスのみを発見できます </a> </li>
<li> <a href="javascript:void(0)">ACTION_STATE_CHANGEDがSTATE_ONであるかを観察する 現在のBluetoothの状態がSTATE_ONでない場合、APIはfalseを返します 現在が更新可能な値の状態であることを確認するために使用します <img src="/images/bluetooth/android_state.png" alt="Cover" class="w-full prose-img"> </a> </li> <li> <a href="javascript:void(0)">使用するターゲットバージョンがBuild.VERSION_CODES#R以下の場合 ユーザーにManifest.permission#BLUETOOTH_ADMIN権限を要求する必要があります <img src="/images/bluetooth/android_R.png" alt="Cover" class="w-full prose-img" > </a> </li> <li> <a href="javascript:void(0)"> 使用するターゲットバージョンがBuild.VERSION_CODES#S以上の場合 ユーザーにManifest.permission#BLUETOOTH_SCAN権限を要求する必要があります <img src="/images/bluetooth/android_S.png" alt="Cover" class="w-full prose-img" > </a> </li> <li> <a href="javascript:void(0)">それ以外の場合 Manifest.permission#ACCESS_FINE_LOCATION権限を要求することができます インタラクティブなBluetoothデバイスの種類を増やすために もちろん、<b>uses-permission</b>にusesPermissionFlags="neverForLocation"タグを追加して 位置情報権限の要求を避けることもできます しかし、その場合、検出できるデバイスの種類が制限されます </a> </li> </ol> </div> <div class="c-border-content-title-4">fun startScan ( callback:ScanCallback )</div> <div class="table_container"> <ol class="rectangle-list"> <li><a href="javascript:void(0)">Bluetooth LEスキャンを開始し、スキャン結果はcallbackを通じて返されます</a></li> <li><a href="javascript:void(0)">フィルターがないため、 省電力のために画面がオフになるとstopScanし、 再度オンにするとresumeします</a></li> <li><a href="javascript:void(0)">使用するターゲットバージョンがBuild.VERSION_CODES#Q以上の場合、 ユーザーにManifest.permission#ACCESS_FINE_LOCATION権限を要求する必要があります</a></li> <li> <a href="javascript:void(0)">使用するターゲットバージョンがBuild.VERSION_CODES#R以下の場合、 ユーザーにManifest.permission#BLUETOOTH_ADMIN権限を要求する必要があります <img src="/images/bluetooth/android_R.png" alt="Cover" class="w-full prose-img"> </a> </li> <li><a href="javascript:void(0)">使用するターゲットバージョンがBuild.VERSION_CODES#S以上の場合、 ユーザーにManifest.permission#BLUETOOTH_SCAN権限を要求する必要があります <img src="/images/bluetooth/android_S.png" alt="Cover" class="w-full prose-img"> </a> </li>
</li> <li><a href="javascript:void(0)">それ以外にも、Manifest.permission#ACCESS_FINE_LOCATION権限を要求することができます。 これにより、インタラクティブなBluetoothデバイスの種類が増えます。 もちろん、<uses-permission>タグにusesPermissionFlags="neverForLocation"を追加して、 位置情報の権限を要求しないようにすることもできますが、 その場合、検索できるデバイスの種類が制限されます。</a></li>
- 特性には上記のstartScan ( callback:ScanCallback ) の6つの項目が含まれます。
-
ScanFilterを使用してスキャン結果をフィルタリングします。
主に以下の項目をサポートしています。 <img src="/images/bluetooth/android_filter.png" alt="bluetooth android filter" style="width: 80%" class="prose-img"> </a> </li> <li><a href="https://developer.android.com/reference/android/bluetooth/le/ScanSettings#summary" target="_blank" rel="noopener noreferrer"> ScanSettingsを使用して、コールバックに対してどのように処理するかを設定します。 例:フィルタリングに成功したデータをすべて返す、最初にフィルタリングに成功したデータのみを返す...など。</a></li>
実際の開発:Bluetoothスキャンの方法
manifestに上記の必要な権限を追加します。
汎用的に使用できます。
requestMultiplePermissions(Manifest.permission.ACCESS_FINE_LOCATION,...
private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
BluetoothDevice.ACTION_FOUNDを監視するために登録します。
val filter = IntentFilter(BluetoothDevice.ACTION_FOUND)
requireContext().registerReceiver(receiver, filter)
BroadcastReceiverを継承します。
そして、receiverタイプの形式で結果をbleDeviceとして返します。
private val receiver = DeviceListBoardCast { bleDevice ->
deviceViewModel.addDevice(bleDevice)
}
資料を取得する方法として、スキャンされたデータは以下から取得できます
val device: BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)!!
val rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE).toInt()
val uuidExtra = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID)
継承したBroadcastReceiverの実装
前述の通り、bluetoothAdapterを取得し、ブロードキャストリスナーを登録しました
したがって、startDiscoveryとcancelDiscoveryを使用してスキャンを開始または終了できます
bluetoothAdapter.startDiscovery()
bluetoothAdapter.cancelDiscovery()
この関数は
主にスキャンのオンオフを行います
viewmodelとcoroutineを組み合わせて
viewmodelでリフレッシュ状態を記録し、coroutineで指定秒数 x 秒スキャンを行います
それほど複雑にする必要がない場合は
startDiscoveryとcancelDiscoveryを直接使用して開発するだけで十分です
スキャンの結果は先ほどのDeviceListBoardCast {}内に返されます
ここはプロジェクトに応じて調整してください
私はviewmodelを使用してデータを観察しています
private val receiver = DeviceListBoardCast { bleDevice ->
deviceViewModel.addDevice(bleDevice)
}
実際の開発:Bluetooth接続の方法
ここではserviceを使用して接続します
まず、serviceを作成します
次にBinderを作成します
onBind時にインスタンスをfragmentに返すために使用します
そのservice内でinitialize()関数を作成します
後でbindservice時に初期化を呼び出すために使用します
次にgattCallbackインスタンスを作成します
ここでは主にonConnectionStateChange、onServicesDiscovered、onCharacteristicReadを使用します
それぞれ、新しい接続状態の変更、新しいサービスの発見、新しいデータの読み取り後に
返されるコールバックです
ここでは主にあなたのニーズに応じて
判断を行います
-
onConnectionStateChange -> Bluetoothの状態を返します
-
discoverServices()を実行して既存のBLEを探します
見つかった場合はonServicesDiscoveredに進みます
- setCharacteristicNotificationというメソッドがあります
これは通知を有効にするためのものです
特定のCharacteristicを探します
(ここでのCharacteristicはハードウェアのプロトコルや定義に依存します)
Bluetoothデバイスの値が変更されると、onCharacteristicChangedで通知されます。
- 次に、writeCharacteristicを使用すると、指定されたCharacteristicに値を書き込むことができます。
結果があると、
onCharacteristicWriteに入ります。
gattCallbackの例:
実際には、以下の2つの部分で接続を行います。
val device = bluetoothAdapter!!.getRemoteDevice(address)
と
bluetoothGatt = device.connectGatt(this, false, gattCallback)
接続したいアドレスを渡し、
接続したいBluetoothDeviceを取得します。
次に、deviceのconnectGattメソッドを使用してGattデバイスをバインドします。
もちろん、前に作成したgattCallbackも渡します。
前の部分は一連のnull確認を行うだけです。
アプリがnullによってクラッシュしないようにします。
gattCallbackのインスタンス化の中で、
broadcastUpdateというメソッドがあることに気づくでしょう。
このメソッドは主にブロードキャストメッセージを送信するためのものです。
自分のニーズに応じて、どのような状況で何をするか、
またはどのようなブロードキャストメッセージを返すかを定義できます。
簡単な接続とデバイスの検索
大体こんな感じです。
次に、Bluetoothで最も重要なのは端末間の通信です。
データを送受信したい場合は、
serviceとcharacteristicを見つける必要があります。
ここで図を示します。

次の方法で見つけます:
前にブロードキャストで取得したgatt serviceを渡すと、
遍歴してcharacteristicを取得できます。
Android公式がcharacteristicのクラスを既に用意しているので、
読み取るには関連する関数を呼び出すだけです。
そして、以前に定義したBluetoothGattCallback内の
onCharacteristicReadで結果が返されます。
ブロードキャストを受信するように定義するだけでデータを取得できます。
さらに、Bluetoothにはnotifyメソッドもあります。
同様に結果が返されます。
BluetoothGattCallback内の
onCharacteristicChangedを確認します。
サードパーティツールを使用して
Bluetoothパケットをキャプチャする方法を見たい場合は、以下を参照してください。
Bluetoothモジュールノート: クラシックBluetooth(BT)と低消費電力Bluetooth(LTE)
Bluetooth 4.0未満のモジュールを指す
一般的にデータ量が多い伝送に使用される
例:音声、音楽、高データ量の伝送など
クラシックBluetoothモジュールはさらに細分化される
従来のBluetoothモジュールと高速Bluetoothモジュール
従来のBluetoothモジュールは2004年に登場
主にBluetooth 2.1プロトコルをサポートするモジュールが代表的
従来のBluetoothには3つの出力レベルがある
Class1 / Class2 / Class3
それぞれ100m / 10m / 1mの伝送距離をサポート
高速Bluetoothモジュールは2009年に登場
速度は約24Mbpsに向上
従来のBluetoothモジュールの8倍
Bluetooth 4.0以上のモジュールを指す
低消費電力Bluetooth技術は低コスト、短距離
2.4GHz ISM周波数帯で動作可能
BLE技術は非常に高速な接続方式を採用しているため
通常は「非接続」状態にしておくことができる(エネルギーを節約)
AndroidスマートフォンのBluetooth 4.xはすべてデュアルモードBluetooth(クラシックBluetoothと低消費電力Bluetoothの両方を持つ)
Kotlin + Jetpack Compose Bluetoothアプリの例
最後に、以前書いた例を最近整理してアップしました。必要な方は参考にしてください こちらを参照