はじめに
Android BLE用のライブラリであるSweetBlueが公開しているAndroid BLE Issuesという文書を日本語に訳しました。
この文書にはAndroidのBLE開発での注意点、機種固有問題等が記載されています。
AndroidのBLEアプリ開発の際に参考にして頂ければ幸いです。
※翻訳に際して、SweetBlueの開発元であるiDevices社から許可を得ています。
SweetBlueの紹介
AndroidのBLEは機種依存問題やAndroid自身(チップやプロトコル・スタック等)に起因すると思われる様々な問題があります。
SweetBlueはそんなAndroid BLEの辛いところを良い感じに吸収してくれるプロプライエタリなライブラリです。
翻訳元のページについて
SweetBlueのGithubのWikiに掲載されている以下のページを翻訳しました。
Android BLE Issues
Android BLE Issues
これは、Android FrameworkのBLE APIをそのまま使った場合に遭遇する可能性のある現象のリストです。
Android 8.1
BLEスキャン
-
Android 8.1にはバッテリー消費量削減のためのドキュメントに記載されていない変更が入っています。この変更の影響により、ScanFilterを使わずにBLEスキャンを実行するとスクリーンオフ状態の時にスキャン結果が通知されなくなります。フォアグラウンドサービスになっていたとしてもスキャン結果は通知されません。SweetBlueではScanFilterクラスを使わない実装をしていたためにこの現象を発見しました。
-
AOSPのIssue Tracker:https://issuetracker.google.com/issues/70619940
-
私達は、より柔軟にBLEスキャン結果をするために正規表現やワイルドカードの機能追加をAOSPに提案しました。https://issuetracker.google.com/issues/77544858. しかし、残念ながらGoogleは仕様としてこのIssueをクローズしました。
-
SweetBlueのVersion 2.52.16でこのBLEスキャンの問題を回避できるよう対応しましたが、将来のAndroid OSではこのワークアラウンドが機能しなくなる可能性があります。
Android Marshmallow
-
Androidの全てのメジャーバージョンのリリースにはBLEの実装の新しい試みが含まれますが、Marshmallowは特に複雑です。
-
BLEスキャンがスキャン結果を返すのは、(A)位置情報パーミッション(AndroidManifest.xml)、(B)ランタイムパーミッションが有効で、(C)位置情報サービスがONの場合のみです。(B)はAndroid Mで導入された仕様で、ユーザーに"Dangerousパーミッション"の確認を強制します。もし(A)と(B)が満たされ、(C)がOFFの場合、SweetBlueはClassic Discoveryモードで動作します。このモードではスキャン結果にはデバイス名とBDアドレスしか含まれませんが、何も通知されないよりはましです。Classic Discoveryモードの場合でも、たとえば目的のBLEデバイスのデバイス名が固定でユニーク値であれば、その情報を元にデバイスをフィルターすることができます。もし(A)と(B)が有効になっていない場合、BLEスキャンは動作しません。位置情報のパーミッションが必要な理由は、位置情報の取得にGPSだけでなくBLEのAdvertiseパケットを拾う必要があるからです。Android M以降のBLEスキャンの詳細に関しては、こちらを御覧ください。また、BluetoothEnablerはこの複雑なプロセスを簡単に扱うことができます。
-
BluetoothAdapterは4つのpublicな状態を持っています。STATE_ON、STATE_TEURNING_OFF、STATE_OFF、STATE_TURNING_ONです。これに加えてPrivateな3つの状態を持っています。それは、STATE_BLE_TURNING_ON、STATE_BLE_ON、STATE_BLE_TURNING_OFFです。BLEに関する状態はprivateなので問題にならないと思うかもしれませんが、それは間違いです。これらの状態はランダムに変化しますので、BroadcastReceiverに通知される状態に注意する必要があります。BroadcastReceiverでの状態の通知に加えて、BluetoothAdapter.getLeState()(privateなメソッドなのでリフレクションで呼び出します)を使って3つのBLE状態をポーリングする必要があります。言うまでもありませんが、ここにもトリッキーなエッジケースが存在します。
BLE接続
-
予期しない切断が発生することがたくさんあります。いくつかの状況では電波強度に関わらず数分毎に切断されてしまうこともあります。そのため再接続に関するロジックを入れておく必要があります。
-
BLE接続する時には謎のBoolean型フラグ “autoConnect”を設定する必要があります。いくつかのAndroid端末とBLEデバイスの組み合わせではtrueに、他の場合はfalseに設定する必要があります(trueにすると接続は成功しますが接続にかかる時間が遅くなります)。つまり、端末毎に“autoConnect”が有効な場合とそうでない場合があるため、このフラグを使う場合は端末毎の動作を事前に把握して使うかどうか判断する必要があります。
-
“autoConnect”フラグを使う場合は注意が必要です。設定後、NativeのBluetoothプロトコル・スタックはかなりアグレッシブにBLE接続をトライし続けます。
-
初回のBLE接続、GATTサービス検索は失敗することがありますが、2〜3回試すと何事もなく成功します。UX向上のためにはそれぞれの操作を数回繰り返したほうが良いでしょう。
-
GATT接続成功のコールバックが通知されても実際には接続されていないことがあります。また、GATT切断のコールバックが通知されてもAndroid内部ではBLE接続されたままになっていることがあります。
-
Bluetoothスタックから通知される全ての状態(接続状態、ペアリング状態、Bluetooth ON/OFF状態)は、状態が古かったり不正確なことがあります。ある状態を通知するコールバックの中で、状態を確認するメソッドを呼び出すと状態が不一致であることがあります。NativeのBluetoothスタックの状態を正確に推測するためには、コールバックで通知された状態と能動的に取得した状態をパラレルで保持しておく必要があります。
-
Nativeの”reconnect”メソッドは、どのデバイスでもまったく動作しませんので、BLE再接続ループとCPU Wake lockをあなた自身で管理する必要があります。
BLEスキャン
- KitkatとLollipop以降ではBLEスキャンに関するAPIが全体的に変更されています。KikatとLollipop以降の両方に対応する場合、動作中にAndroidのバージョンをチェックしBLEスキャンの処理を分ける必要があります。
- BLEスキャンが理由もなく失敗した場合、Classic BluetoothのDiscoveryを代わりに使う方法がありますが、BLEスキャン違いがあるため取り扱いが難しいです。
- BLEスキャンが失敗した後に、しばらく待ってからスキャンすると成功することがあります。別スレッドから周期的にBLEスキャンをリトライする方法が有効です(BLEスキャン開始→停止、を最低数百ミリ秒の間隔を空けて繰り返す)
- KitkatのBLEスキャン用のフィルタは動作しないと考えたほうが良いかもしれません。代替案として、フィルタ無しで全てのデバイスをスキャンし、スキャン結果をフィルタする方法があります。
- BLEスキャンをかけっぱなしにすることはできません。BLEスキャンをかけ続けるとそのうちスキャン結果が通知されなくなります。また、バッテリーの消費量が増え、BLEの動作が不安定になります。前述のように間隔を空けて周期的にスキャンを繰り返す必要があります。
ペアリング/ボンディング
- いくつかのAndroid端末では、暗号化必須のキャラクタリスティクスの操作に問題があります。
- 未ペアリングの状態で暗号化必須のキャラクタリスティクスの読み書きを試行すると、Android Frameworkは自動的にペアリングのシーケンス開始します。この時、キャラクタリスティクスの読み書きのリトライは行われないことがありますが、リトライされることもありますので注意が必要です。
- いくつかのAndroid端末では再接続の際にペアリング情報が再利用されないことがあります。そのため、再接続する際は一度ペアリング情報をクリアしてから再ペアリングする必要があります。
- LG G4、Samsung S6等いくつかのAndroid端末は機器固有のボンディングに関する現象があります。PublicなBluetoothDevice.createBond()の代わりにPrivateなBLE用のメソッドをリフレクションで呼び出す必要があります。
スレッド
- ほとんどの操作はメインスレッドから行うとより動作が安定します。しかし、いくつかの操作はランダムに数秒間ブロックするのでメインスレッドから操作する場合は注意が必要です。どの操作がブロックするのか把握し、いつ・どのようにスレッドを分けるのかハンドルすることが重要です。
- Android Frameworkは、複数の異なるスレッドで非同期のコールバックを通知します。ほとんどの場合、呼び出しスレッドとは別のスレッドで通知されるのでUIを更新する場合はメインスレッドに切り替える必要があります。
GATTキャラクタリスティクスに対するRead/Write/Notify
- 一度に複数の操作をすることはできません。2回Writeしたい場合、1回目のWriteの完了を待ってから次のWriteを実行する必要があります。
- Notify/Read のキャラクタリスティクスにおいてReadできるにも関わらずNotifyの通知が止まってしまうことがあります。この場合、一定間隔でReadしてポーリングする必要があります。
- 同時に登録できるNotifyのキャラクタリスティクス数に制限があります。制限される数は、ある端末では4、他の端末では7だったりします。この場合も、前述のようにReadキャラクタリスティクスをポーリングする方法が有効です。
- キャラクタリスティクスのReadが成功してもNULLの値が入っていることがあります。
- RSSI値(電波強度)の値はAndroid端末によって様々ですので、RSSI値から距離を推定することは難しいです。端末毎のRSSI値の特性を調査してRSSI値を変換、調節してあげる必要があります。
- GATTサービス検索では、Android Frameworkの内部にキャッシュされた結果を誤って返してくることがあります。BLEデバイス側のGATTサービス構成が変更されていた場合に、誤ったGATTサービス検索結果を使うとおかしなことになるので注意が必要です。
その他
- BLE接続、切断、GATTキャラクタリスティクスの読み書きなど、全ての操作でコールバックが返らないケースがあります。全ての操作に対してタイムアウトを設ける必要があります。
- Googleのドキュメントに記載されていないエラーコードが返ることがあります。
- いくつかの重要なメソッドはリフレクションを使って呼び出す必要があります(ペアリング等)
- Bluetoothデバイス名のフォーマットは機器によって様々で、動作中に変化する可能性があります。スペースとアンダースコアが入れ替わったり、ある時はキャメルケース、小文字、最後にハイフン付きでBDアドレスの下位4桁が付加されることもあります。デバイス名でフィルタリングしたりデバイス名をUIに表示する場合は、これらの点について注意することが必要です。
- いくつかのNativeスタックに対するメソッドは、ドキュメントに記載されていないDeadObjectExceptionsというExceptionをthrowします。これはドキュメントに未記載の動作でごくまれに発生します。
- BluetoothのON/OFFができなくなることがあります。SweetBlueはこの現象を修正することはできませんが、UhOhListenerで検知することができます。現象発生時には端末の再起動が必要です。
機種依存問題
Samsung Galaxy S7(Marshmallow)
- S7には"Nearby device scanning”と呼ばれる新しい機能が入っています。この機能をONにすると、S7は近くにあるデバイスをBLEスキャンします。この機能はBluetoothがオフに設定されていてもBLEスキャンを使います。そして、Lollipopより前のBLEスキャンAPIを使っている場合、スキャン結果に予測不可能な影響を及ぼします(SweetBlueでは、デフォルトもしくはBleScanMode.AUTOにした時に使っているAPIです)。この現象を回避するには、設定画面から"Nearby device scanning”機能をオフにするか、BleScanMode.POST_LOLLIPOPを使う必要があります。
OnePlus OnePlus2 と Motorola Moto X Pure
- この2つのAndroid端末では、20バイトより大きいMTU値を設定することができますが、設定したMTUサイズでGATTキャラクタリスティクスにWriteすると要求はタイムアウトします。テストの結果、この2つの機種で安定して動作するMTUの最大値は50です。