Android プラットフォームでは Bluetooth ネットワーク スタックをサポートしているため、端末はワイヤレスで他の Bluetooth 端末とのデータを交換できます。このアプリのフレームワークにより、Android Bluetooth API を介した Bluetooth 機能へのアクセスが可能になります。 これらの API を使用して、アプリは他の Bluetooth 端末とワイヤレスで接続し、ポイントツーポイントおよびマルチポイントのワイヤレス機能を使用できます。
Bluetooth API を使用して、Android アプリは次の操作を実行できます。
- 他の Bluetooth 端末をスキャンする
- ペア設定された Bluetooth 端末のローカル Bluetooth アダプタを問い合わせる
- RFCOMM チャンネルを確立する
- Service Discovery を使用して他の端末に接続する
- 他の端末とデータを送受信する
- 複数の接続を管理する
このドキュメントでは、クラシック Bluetooth の使用方法について説明します。クラシック Bluetooth は、ストリーミングや Android 端末との通信など、電池の消費量が多い操作に適した選択肢です。 省電力要件がある Bluetooth 端末向けに、Android 4.3(API レベル 18)で Bluetooth Low Energy の API サポートが導入されました。 詳細については、Bluetooth Low Energy をご覧ください。
基本
このドキュメントでは、Android Bluetooth API を使用して、Bluetooth 通信に不可欠な 4 つの主なタスクを完了する方法について説明します。そのタスクとは、Bluetooth の設定、ペア設定済みの端末またはローカル エリアで使用可能な端末の検索、端末の接続、端末間でのデータ通信です。
Bluetooth API はすべて、android.bluetooth パッケージ内にあります。
次に、Bluetooth 接続を作成するために必要なクラスとインターフェースの概要を説明します。
BluetoothAdapter- ローカル Bluetooth アダプタ(Bluetooth 無線通信)を表します。
BluetoothAdapterは、Bluetooth のあらゆる操作のエントリ ポイントになります。 これを使用して他の Bluetooth 端末を検出し、ボンディング(ペア設定)した端末のリストを問い合わせて、既知の MAC アドレスを使用してBluetoothDeviceをインスタンス化し、BluetoothServerSocketを作成して他の端末の通信をリッスンします。 BluetoothDevice- リモート Bluetooth 端末を表します。これを使用して、
BluetoothSocketを介してリモート端末と接続したり、名前、アドレス、クラス、ボンディング状態など端末に関する情報を問い合わせたりします。 BluetoothSocket- Bluetooth ソケットのインターフェースを表します(TCP
Socketと同様)。これは、アプリが InputStream および OutputStream を介して別の Bluetooth 端末とデータ交換するための接続点です。 BluetoothServerSocket- 受信リクエストをリッスンするためにオープンしているサーバー ソケットを表します(TCP
ServerSocketと同様)。 2 つの Android 端末を接続するには、一方の端末がこのクラスを使用してサーバー ソケットをオープンする必要があります。 リモート Bluetooth 端末はこの端末への接続リクエストを行い、接続が受け入れられると、BluetoothServerSocketが接続されたBluetoothSocketを返します。 BluetoothClass- Bluetooth 端末の一般的な特性と機能を示します。 これは端末の大 / 小のクラスとサービスを定義する読み取り専用のプロパティのセットです。 ただし、これは端末によってサポートされるすべての Bluetooth のプロファイルとサービスについて確実に記述したものではなく、端末の種類を識別するヒントとして活用できます。
BluetoothProfile- Bluetooth プロファイルを表すインターフェース。 Bluetooth プロファイルは、端末間で Bluetooth ベースの通信を行うためのワイヤレス インターフェース仕様です。 その一例が、Hands-Free プロファイルです。 プロファイルの詳細については、プロファイルの使用をご覧ください。
BluetoothHeadset- スマートフォンで使用する Bluetooth ヘッドセットをサポートします。 これには、Bluetooth Headset と Hands-Free(v1.5)の両方のプロファイルが含まれています。
BluetoothA2dp- 端末間で Bluetooth 接続によって高品質のオーディオをストリーミングできる方法を定義しています。"A2DP" は、Advanced Audio Distribution Profile(高度オーディオ配信プロファイル)の略です。
BluetoothHealth- Bluetooth サービスを制御する Health Device Profile プロキシを表します。
BluetoothHealthCallbackBluetoothHealthコールバックの実装に使用する抽象クラス。このクラスを拡張してコールバック メソッドを実装し、アプリの登録状態と Bluetooth チャンネルの状態の変更に関するアップデートを受信します。BluetoothHealthAppConfiguration- Bluetooth Health サードパーティ アプリが、リモート Bluetooth ヘルス機器と通信するために登録したアプリの設定を表します。
BluetoothProfile.ServiceListener- サービスへの接続時およびサービスから切断時に
BluetoothProfileIPC クライアントに通知するインターフェース(つまり、特定のプロファイルを実行する内部サービス)。
Bluetooth のパーミッション
アプリで Bluetooth 機能を使用するには、BLUETOOTH で Bluetooth パーミッションを宣言する必要があります。このパーミッションは、接続のリクエスト、接続の受け入れ、データの転送など、Bluetooth 通信を実行するために必要です。
アプリで端末の検出を開始したり Bluetooth の設定を操作する場合は、BLUETOOTH_ADMIN パーミッションも宣言する必要があります。
ほとんどのアプリは、ローカル Bluetooth 端末の検出のみのためにこのパーミッションが必要です。
このパーミッションで付与されたその他の機能は、アプリがユーザーのリクエストに応じて Bluetooth の設定を変更する「Power Manager」として機能する場合を除いて、使用すべきではありません。
注: BLUETOOTH_ADMIN パーミッションを使用する場合は、BLUETOOTH パーミッションも必要です。
アプリのマニフェスト ファイルで Bluetooth のパーミッションを宣言してください。次に例を示します。
<manifest ... > <uses-permission android:name="android.permission.BLUETOOTH" /> ... </manifest>
アプリのパーミッション宣言の詳細については、<uses-permission> のリファレンスをご覧ください。
Bluetooth の設定
図 1: Bluetooth を有効にするダイアログ
アプリで Bluetooth 通信を行う前に、その Bluetooth が端末でサポートされているか、サポートされている場合は有効かどうかを確認する必要があります。
Bluetooth がサポートされていない場合は、すべての Bluetooth 機能を適切に無効化してください。
Bluetooth がサポートされているものの無効化されている場合、アプリから離れることなく、ユーザーが Bluetooth を有効化するようにリクエストすることができます。
この設定は、BluetoothAdapter を使用して、2 つのステップで完了します。
BluetoothAdapterを取得するすべての Bluetooth のアクティビティには
BluetoothAdapterが必要です。BluetoothAdapterを取得するには、静的メソッドgetDefaultAdapter()を呼び出します。これは、端末独自の Bluetooth アダプタ(Bluetooth 無線通信)を表すBluetoothAdapterを返します。 システム全体には 1 つの Bluetooth アダプタが存在し、アプリはこのオブジェクトを使用して Bluetooth アダプタとやり取りができます。getDefaultAdapter()が null を返した場合、端末は Bluetooth をサポートしていないため、ここで終了です。 次に例を示します。BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { // Device does not support Bluetooth }- Bluetooth を有効化する
次に、Bluetooth が有効であることを確認する必要があります。
isEnabled()を呼び出して、Bluetooth が現在有効かどうかを確認します。 このメソッドが false を返したら、Bluetooth は無効です。Bluetooth の有効化をリクエストするには、ACTION_REQUEST_ENABLEアクション インテントを指定してstartActivityForResult()を呼び出します。これにより(アプリを停止せずに)システム設定で Bluetooth を有効化するようにリクエストを発行します。 次に例を示します。if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }図 1 に示すように、ユーザーに Bluetooth を有効化するためのパーミッションを求めるダイアログが表示されます。ユーザーが [Yes] を選択すると、システムは Bluetooth の有効化を開始し、プロセスが完了(または失敗)するとフォーカスをアプリに戻します。
startActivityForResult()に渡されるREQUEST_ENABLE_BT定数は、ローカルで定義された整数(1 以上)です。システムはこの値をrequestCodeパラメータとしてonActivityResult()の実装に渡します。Bluetooth の有効化に成功した場合、アクティビティは
onActivityResult()コールバックで結果コードRESULT_OKを受け取ります。 エラー(またはユーザーが [No] を選択)により Bluetooth が有効化されなかった場合、結果コードはRESULT_CANCELEDです。
オプションで、アプリは ACTION_STATE_CHANGED ブロードキャスト インテントをリッスンします。このインテントは Bluetooth の状態が変更されるたびに、システムによってブロードキャストされます。
このブロードキャストには追加フィールドの EXTRA_STATE と EXTRA_PREVIOUS_STATE が含まれており、それぞれ Bluetooth の以前の状態と新しい状態が格納されています。
これらの追加フィールドで使用できる値は、STATE_TURNING_ON、STATE_ON、STATE_TURNING_OFF、STATE_OFF です。
このブロードキャストのリッスンは、アプリの実行中に Bluetooth 状態の変更を検出するうえで便利です。
ヒント: 検出の許可を有効にすると、自動的に Bluetooth が有効になります。 Bluetooth アクティビティを実行する前に、端末の検出の許可を一貫して有効にするつもりであれば、上記のステップ 2 をスキップすることができます。 以下の検出の許可を有効化するをご覧ください。
端末の検索
BluetoothAdapter を使用して、端末の検出を使用するか、ペア設定(ボンディング)された端末のリストを問い合わせて、リモート Bluetooth 端末を見つけることができます。
端末の検出はローカル エリアで Bluetooth が有効な端末を検索し、各端末の情報をリクエストするスキャン手順です(この処理は、「検出」「問い合わせ」「スキャン」と呼ばれる場合もあります)。ただし、ローカル エリア内の Bluetooth 端末は、現在検出可能になっている場合にのみ検出リクエストに応答します。 端末が検出可能な場合、端末名、クラス、一意の MAC アドレスなどの情報を共有することで検出リクエストに応答します。 検出を実行した端末はこの情報を使用して、検出された端末への接続開始を選択できます。
リモート端末との接続が初めて確立されるとペア設定リクエストが自動的にユーザーに表示されます。 端末がペア設定されると、端末の基本情報(端末名、クラス、MAC アドレス)が保存され、Bluetooth API を使用して読み取れるようになります。 リモート端末の既知の MAC アドレスを使用すると、検出を実行しなくてもいつでも接続を開始できます(端末が範囲内に存在することが前提です)。
ペア設定と接続には違いがある点に注意してください。ペア設定は、端末がお互いの存在を認識し、認証に使用できる共有のリンクキーを持ち、相互に暗号化された接続を確立できることを意味します。 接続は、端末が現在 RFCOMM チャンネルを共有し、相互にデータを送信できることを意味します。 現在の Android Bluetooth API では、RFCOMM 接続を確立する前に端末のペア設定が必要です (Bluetooth API を使用して暗号化された接続を開始すると自動的にペア設定が実行されます)。
次のセクションでは、ペア設定済みの端末を見つける、または端末の検出を使用して新しい端末を見つける方法について説明します。
注: Android 搭載端末は、デフォルトでは検出可能になっていません。 ユーザーは、システム設定を使用して一定の時間だけ端末を検出可能にすることができます。またはアプリで、ユーザーがアプリから離れることなく、検出の許可を有効化するようにリクエストできます。 検出の許可を有効化する方法については、以下で説明します。
ペア設定済みの端末の問い合わせ
端末検出を実行する前に、ペア設定済みの端末のセットを問い合わせて、目的の端末を既に認識しているかどうかを問い合わせることをお勧めします。
問い合わせるには、getBondedDevices() を呼び出します。
これは、ペア設定済みの端末を表す BluetoothDevice のセットを返します。
たとえば、ペア設定済みの端末をすべて問い合わせて、ArrayAdapter を使用して各端末の名前をユーザーに表示することができます。
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
接続を開始するために BluetoothDevice オブジェクトから取得する必要があるのは、 MAC アドレスだけです。
この例では、ユーザーに表示する ArrayAdapter の一部として保存されています。
その後、MAC アドレスを抽出して接続を開始できます。
接続の確立の詳細については、端末の接続に関するセクションをご覧ください。
端末の検出
端末の検出を開始するには、startDiscovery() を呼び出すだけです。このプロセスは非同期で実行され、メソッドは検出が正常に開始されたどうかを示す boolean をすぐに返します。
検出プロセスでは通常、約 12 秒間の問い合わせのスキャンが行われ、その後、検出された各端末のページ スキャンによってそれぞれの Bluetooth 名を取得します。
検出された各端末に関する情報を受信するには、アプリで ACTION_FOUND インテントの BroadcastReceiver を登録する必要があります。
端末ごとに、システムは ACTION_FOUND インテントをブロードキャストします。
このインテントには追加フィールドの EXTRA_DEVICE と EXTRA_CLASS があり、それぞれ BluetoothDevice と BluetoothClass を格納しています。
たとえば、端末が検出されたときに、ブロードキャストを処理するために登録する方法は次のとおりです。
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
接続を開始するために BluetoothDevice オブジェクトから取得する必要があるのは、 MAC アドレスだけです。
この例では、ユーザーに表示する ArrayAdapter の一部として保存されています。
その後、MAC アドレスを抽出して接続を開始できます。
接続の確立の詳細については、端末の接続に関するセクションをご覧ください。
警告: 端末の検出を実行すると Bluetooth アダプタに負荷がかかり、アダプタの多くのリソースを消費します。
接続する端末が見つかったら、接続を試みる前に、cancelDiscovery() を使用して必ず検出を停止してください。
また、既に端末との接続を確立している場合、検出を実行すると接続に使用できる帯域幅が大幅に減少するため、接続中は検出を実行しないでください。
検出の許可の有効化
ローカル端末を他の端末から検出可能にするには、ACTION_REQUEST_DISCOVERABLE アクション インテントを指定して startActivityForResult(Intent, int) を呼び出します。
これはシステム設定を使用して(アプリを停止することなく)検出可能モードを有効にするリクエストを発行します。
デフォルトでは、端末は 120 秒間検出可能になります。
EXTRA_DISCOVERABLE_DURATION インテントをさらに追加して、別の時間を定義することもできます。
アプリが設定できる最長時間は 3,600 秒で、値 0 は端末が常に検出可能であることを意味します。
0 未満または 3,600 より大きい値を指定すると、自動的に 120 秒に設定されます。
たとえば、このスニペットでは時間を 300 秒に設定します。
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent);
図 2: 検出の許可を有効にするダイアログ
図 2 に示すように、端末を検出可能にする許可をユーザーに求めるダイアログが表示されます。ユーザーが [Yes] を選択すると、端末は指定された時間、検出可能になります。
アクティビティは onActivityResult()) コールバックの呼び出しを受け取ります。この結果コードは、端末を検出可能にする時間と同じです。
ユーザーが [No] を選択するかエラーが発生した場合、結果コードは RESULT_CANCELED になります。
注: Bluetooth が端末で有効になっていない場合、端末の検出の許可を有効にすると、Bluetooth が自動的に有効になります。
端末は割り当てられた時間、検出可能モードをサイレントに継続します。検出可能モードに変化があったときに通知を受けたい場合は、ACTION_SCAN_MODE_CHANGED インテントの BroadcastReceiver を登録できます。
これには、追加フィールドの EXTRA_SCAN_MODE と EXTRA_PREVIOUS_SCAN_MODE が含まれており、それぞれ新しいスキャン モードと以前のスキャン モードを示します。
それぞれに使用できる値は、SCAN_MODE_CONNECTABLE_DISCOVERABLE、SCAN_MODE_CONNECTABLE、または SCAN_MODE_NONE です。これは、端末が検出可能モードになっている、検出可能モードではないがまだ接続を受信できる、検出可能モードではなく接続も受信できない、のいずれかを示します。
リモート端末との接続を開始しようとするときに、端末の検出の許可を有効にする必要はありません。 リモート端末は、接続を開始する前に端末を検出できていることが前提であるため、検出の許可の有効化は、アプリで接続要求を受け入れるサーバー ソケットをホストしなければならない場合に限られます。
端末の接続
2 つの端末上のアプリ間で接続を確立するには、サーバー側とクライアント側の両方の仕組みを実装する必要があります。一方の端末はサーバー ソケットをオープンし、他方の端末は接続を開始する必要があるためです(接続を開始するには、サーバー端末の MAC アドレスを使用)。
サーバーとクライアントは、それぞれが同じ RFCOMM チャンネルで接続された BluetoothSocket を取得できれば接続完了と見なされます。
この時点で、各端末は入力ストリームと出力ストリームを取得してデータ転送を開始できます。この内容については、接続の管理のセクションで取り上げます。
このセクションでは、2 つの端末間で接続を開始する方法について説明します。
サーバー端末とクライアント端末はそれぞれ、異なる方法で必要な BluetoothSocket を取得します。サーバーは、接続要求を受け入れたときにソケットを受け取ります。
クライアントは、RFCOMM チャンネルをサーバーに対してオープンしたときにソケットを受け取ります。
図 3: Bluetooth のペア設定のダイアログ
実装のテクニックとして、各端末を自動的にサーバーとして準備しておくと、それぞれがサーバー ソケットをオープンして接続をリッスンできます。そうすると、片方の端末がもう一方の端末との接続を開始して、クライアントになることができます。 または、片方の端末がオンデマンドで明示的に接続を「ホスト」してサーバー ソケットをオープンすると、もう一方の端末は容易に接続を開始できます。
注: 2 つの端末がまだペア設定されていない場合、Android フレームワークは自動的にペア設定リクエストの通知を表示するか、または図 3 に示すように、接続手順の実行中にユーザーにダイアログを表示します。したがって、端末に接続しようとするとき、端末がペア設定済みかどうかをアプリで考慮する必要はありません。 RFCOMM 接続の試行は、ユーザーがペア設定に成功するまではブロックされます。またはユーザーがペア設定を拒否するか、ペア設定が失敗 / タイムアウトしたときには失敗します。
サーバー側の接続
2 つの端末を接続するには、一方がオープンした BluetoothServerSocket を保持してサーバーとして動作する必要があります。
サーバー ソケットの目的は、受信接続リクエストをリッスンして、それを受け入れたときに、接続した BluetoothSocket を提供することです。
BluetoothSocket を BluetoothServerSocket から取得したら、複数の接続を受け入れる必要がない限り、BluetoothServerSocket は破棄できます(破棄する必要があります)。
UUID について
Universally Unique Identifier(UUID)は、情報を一意に識別するために使用される、標準化された 128 ビット形式の文字列 ID です。
UUID のポイントは、ランダムに選択できるほど十分に大きく、クラッシュしないことです。
今回は、アプリの Bluetooth サービスを一意に識別するために使用します。
アプリで使用できる UUID を取得するには、ウェブ上に数多く存在するランダム UUID ジェネレータのうちいずれかを使用し、fromString(String) を使用して UUID を初期化します。
サーバー ソケットを設定して接続を受け入れる基本的な手順は次のとおりです。
listenUsingRfcommWithServiceRecord(String, UUID)を呼び出してBluetoothServerSocketを取得します。この文字列はサービスを識別できる名前です。端末上のシステムは自動的に新しい Service Discovery Protocol(SDP)データベースのエントリを書き込みます(この名前は任意です。単純にアプリ名を指定することもできます)。 UUID は SDP エントリにも含まれており、クライアント端末との接続許可のベースになります。 つまり、クライアントがこの端末との接続を試みたとき、端末は接続に使用したいサービスを一意に識別できる UUID を持っています。 (次のステップで)接続を受け入れるには、この UUID が一致している必要があります。
accept()を呼び出して、接続リクエストのリッスンを開始します。これはブロッキング コールです。接続が受け入れられたか、例外が発生したときに制御が戻ります。 接続が受け入れられるのは、リモート端末が、このリスニング サーバー ソケットで登録した UUID と一致する UUID を指定した接続リクエストを送信した場合だけです。 接続に成功した場合、
accept()は接続されたBluetoothSocketを返します。- 追加の接続を受け入れる必要がない限り、
close()を呼び出します。これにより、サーバー ソケットとそのすべてのリソースが解放されますが、
accept()で返された接続済みのBluetoothSocketはクローズしません。 TCP/IP とは異なり、RFCOMM はチャンネルごとに、同時に 1 つのクライアントのみ接続を許可します。したがってほとんどの場合、接続されたソケットの受け入れ直後にBluetoothServerSocketのclose()を呼び出すのが適切です。
accept() の呼び出しは、メイン アクティビティの UI スレッドで実行しないでください。これはブロッキング コールなので、アプリがその他の操作を受け付けられなくなります。
通常、BluetoothServerSocket または BluetoothSocket の操作はすべて、アプリで管理される新しいスレッドで実行するのが適切な方法です。
accept() のようなブロックされる呼び出しを中止するには、BluetoothServerSocket(または BluetoothSocket)で別のスレッドから close() を呼び出すと、ブロックされた呼び出しからすぐに戻ります。
BluetoothServerSocket または BluetoothSocket のすべてのメソッドがスレッドセーフというわけではないため、注意してください。
例
これは、接続要求を受け入れるサーバー コンポーネントのスレッドを簡略化したものです。
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
BluetoothServerSocket tmp = null;
try {
// MY_UUID is the app's UUID string, also used by the client code
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) { }
mmServerSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
}
/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) { }
}
}
この例では、1 つの接続要求のみが想定されており、接続が受け入れられるとすぐに BluetoothSocket が取得され、アプリは取得した BluetoothSocket を別のスレッドに送信し、BluetoothServerSocket をクローズしてループを終了します。
accept() が BluetoothSocket を返したとき、ソケットは既に接続されているため、(クライアント側で処理したように)connect() を呼び出さないでください。
manageConnectedSocket() はこのアプリの架空のメソッドで、データ転送用のスレッドを開始します。詳細については、接続の管理に関するセクションで説明します。
通常、接続要求のリッスンが完了したらすぐに BluetoothServerSocket をクローズする必要があります。
この例では、BluetoothSocket を取得してすぐ close() が呼び出されています。
また、サーバー ソケットのリッスンを停止する必要がある場合は、スレッド内でプライベートの BluetoothSocket をクローズできるパブリック メソッドを提供することもできます。
クライアント側の接続
リモート端末(オープンなサーバー ソケットを保持している端末)との接続を開始するには、まずリモート端末を表す BluetoothDevice オブジェクトを取得する必要があります(BluetoothDevice の取得については、端末の検索に関する上記のセクションをご覧ください)。
BluetoothDevice を使用して BluetoothSocket を取得し、接続を開始する必要があります。
基本的な手順は次のとおりです。
BluetoothDeviceを使用し、createRfcommSocketToServiceRecord(UUID)を呼び出してBluetoothSocketを取得する。これにより、
BluetoothDeviceに接続するBluetoothSocketを初期化します。 ここで渡される UUID は、サーバー端末が(listenUsingRfcommWithServiceRecord(String, UUID)を使用して)BluetoothServerSocketをオープンしたときに使用した UUID と一致している必要があります。 同じ UUID を使用するには単純に、UUID の文字列をアプリにハードコードして、サーバーとクライアントの両方のコードからその文字列を参照するだけです。connect()を呼び出して接続を開始する。この呼び出しによって、システムは UUID を照合するためにリモート端末の SDP ルックアップを実行します。 ルックアップに成功してリモート端末が接続を受け入れると、その接続で使用する RFCOMM チャンネルが共有され、
connect()から制御が戻ります。 このメソッドはブロッキング コールです。 なんらかの理由で接続に失敗するかconnect()メソッドがタイムアウトになると(約 12 秒後)、このメソッドは例外をスローします。connect()はブロッキング コールであるため、この接続手順は必ずメインのアクティビティ スレッドとは別のスレッドで実行する必要があります。注:
connect()を呼び出すときは必ず、端末の検出を実行していないことを確認してください。 検出を実行していると、接続の試行処理が大幅に遅くなり、失敗する可能性があります。
例
以下は、Bluetooth 接続を開始するスレッドの基本的な例です。
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(mmSocket);
}
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
cancelDiscovery() は、接続が確立される前に呼び出される点に注意してください。
必ず接続前に呼び出してください。これは実際に実行中かどうかを確認せずに呼び出しても問題ありません(どうしても確認したい場合は、isDiscovering() を呼び出します)。
manageConnectedSocket() はこのアプリの架空のメソッドで、データ転送用のスレッドを開始します。詳細については、接続の管理に関するセクションで説明します。
BluetoothSocket の処理が完了したら、必ず close() を呼び出してクリーンアップを実行してください。これにより接続済みソケットがクローズされ、内部のリソースがクリーンアップされます。
接続の管理
2 つ(またはそれ以上)の端末を正常に接続すると、各端末は接続済みの BluetoothSocket を受け取ります。
ここから、いよいよ端末間のデータ共有が始まります。
BluetoothSocket を使用すると、任意のデータを送信するための一般的な手順が簡素化されます。
- ソケットを介した転送を処理する
InputStreamとOutputStreamを取得します。それぞれ、getInputStream()とgetOutputStream()を使用します。 read(byte[])とwrite(byte[])を使用して、ストリームに対してデータを読み書きします。
これだけです。
もちろん、実装の詳細については検討が必要です。まず第一に、すべてのストリームの読み書きには必ず専用のスレッドを使用してください。
その理由は read(byte[]) メソッドと write(byte[]) メソッドがどちらもブロッキング コールであるためです。read(byte[]) は、ストリームからなにかを読み出すまでブロックします。write(byte[]) は通常ブロックしませんが、リモート端末がすぐに read(byte[]) を呼び出さずに中間バッファがいっぱいになったとき、フロー制御のためにブロックする場合があります。したがって、スレッド内のメイン ループは InputStream の読み込み専用にする必要があります。
スレッド内の別のパブリック メソッドを使用して、OutputStream への書き込みを開始することができます。
例
この処理の例を次に示します。
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* Call this from the main activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
コンストラクタが必要なストリームを取得し、実行すると、InputStream からデータを受信するまでスレッドは待機します。
read(byte[]) がストリームから取得したバイトを返すと、親クラスのメンバーである Handler によって、データはメイン アクティビティに送信されます。
元の処理に戻り、ストリームの次のバイトを待機します。
データの送信は、メイン アクティビティからスレッドの write() メソッドを呼び出し、送信するバイトを渡すだけなので簡単です。
このメソッドは、単純に write(byte[]) を呼び出して、データをリモート端末に送信します。
BluetoothSocket をクローズしていつでも接続を終了できるため、スレッドの cancel() メソッドは重要です。Bluetooth 接続の使用を完了したら、必ずこのメソッドを呼び出してください。
Bluetooth API を使用したデモについては、Bluetooth Chat サンプル アプリをご覧ください。
プロファイルの使用
Android 3.0 以降、Bluetooth API では Bluetooth プロファイルの操作がサポートされています。 Bluetooth プロファイルは、端末間で Bluetooth ベースの通信を行うためのワイヤレス インターフェース仕様です。 その一例が、Hands-Free プロファイルです。 スマートフォンをワイヤレス ヘッドセットに接続する場合、どちらの端末も Hands-Free プロファイルをサポートしている必要があります。
インターフェース BluetoothProfile を実装して、特定の Bluetooth プロファイルをサポートする独自のクラスを記述することができます。
Android Bluetooth API は、次の Bluetooth プロファイルの実装を提供しています。
- Headset。Headset プロファイルは、スマートフォンで使用できる Bluetooth ヘッドセットのサポートを提供します。
Android は
BluetoothHeadsetクラスを提供しています。これはプロセス間通信(IPC)を介して Bluetooth Headset Service を制御するためのプロキシです。 これには、Bluetooth Headset と Hands-Free(v1.5)の両方のプロファイルが含まれています。BluetoothHeadsetクラスには、AT コマンドのサポートが含まれています。このトピックの詳細については、ベンダー固有の AT コマンドをご覧ください。 - A2DP。Advanced Audio Distribution Profile(A2DP)プロファイルは、Bluetooth 接続を介して端末間で高品質のオーディオをストリーミングできる方法を定義しています。
Android が提供する
BluetoothA2dpクラスは、IPC を介して Bluetooth A2DP Service を制御するためのプロキシです。 - Health Device。Android 4.0(API レベル 14)で、Bluetooth Health Device Profile(HDP)を導入しました。 このプロファイルを使用すると、Bluetooth を使用して、心拍モニタ、血圧計、体温計、体重計など、Bluetooth をサポートするヘルス機器と通信するアプリを作成できます。 サポートされる端末のリストと対応する端末のデータ専用のコードについては、www.bluetooth.org の Bluetooth Assigned Numbers をご覧ください。 これらの値は、ISO/IEEE 11073-20601 [7] 仕様でも Nomenclature Codes Annex の MDC_DEV_SPEC_PROFILE_* として掲載されています。 HDP の詳細については、Health Device Profile をご覧ください。
プロファイルを操作するための基本的なステップは次のとおりです。
- Bluetooth の設定で説明したとおり、デフォルトのアダプタを取得します。
getProfileProxy()を使用して、プロファイルと関連付けられたプロファイル プロキシ オブジェクトとの接続を確立します。以下の例で、プロファイル プロキシ オブジェクトはBluetoothHeadsetのインスタンスです。BluetoothProfile.ServiceListenerを設定します。このリスナーはBluetoothProfileIPC クライアントに、サービスへの接続またはサービスからの切断を通知します。onServiceConnected()で、プロファイル プロキシ オブジェクトのハンドルを取得します。- プロファイル プロキシ オブジェクトを取得したら、それを使用して接続状態を監視し、プロファイルに関連したその他の操作を実行できます。
たとえば、このコード スニペットは BluetoothHeadset プロキシ オブジェクトに接続して、Headset プロファイルを制御する方法を示しています。
BluetoothHeadset mBluetoothHeadset;
// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = (BluetoothHeadset) proxy;
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = null;
}
}
};
// ... call functions on mBluetoothHeadset
// Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);
ベンダー固有の AT コマンド
Android 3.0 以降、ヘッドセットから送信された事前定義済みのベンダー固有の AT コマンドに対するシステムのブロードキャストを受信するため、アプリを登録できるようになりました(Plantronics +XEVENT コマンドなど)。
たとえば、接続された端末の電池レベルを示すブロードキャストをアプリが受信できれば、ユーザーに通知するなど、必要に応じてアクションを実行できます。
ヘッドセットのベンダー固有の AT コマンドを処理するには、ACTION_VENDOR_SPECIFIC_HEADSET_EVENT インテントのブロードキャスト レシーバーを作成します。
Health Device Profile
Android 4.0(API レベル 14)で、Bluetooth Health Device Profile(HDP)のサポートが導入されました。
これにより、Bluetooth を使用して、心拍モニタ、血圧計、体温計、体重計など、Bluetooth をサポートするヘルス機器と通信するアプリを作成できます。
Bluetooth Health API には、BluetoothHealth、BluetoothHealthCallback、BluetoothHealthAppConfiguration の各クラスが含まれています。クラスの詳細については基本セクションをご覧ください。
Bluetooth Health API を使用するにあたり、HDP の主なコンセプトを理解しておくと役に立ちます。
| コンセプト | 説明 |
|---|---|
| Source | HDP で定義されているロール。 Source は、Android 端末やタブレットなどの端末に医療データ(体重、血糖値、体温など)を送信するヘルス機器です。 |
| Sink | HDP で定義されているロール。
HDP では、Sink は医療データを受信するスマート端末です。
Android HDP アプリでは、Sink は BluetoothHealthAppConfiguration オブジェクトで表されます。
|
| Registration | 特定のヘルス機器に Sink を登録することを指します。 |
| Connection | ヘルス機器と、Android 端末やタブレットなどのスマート デバイス間でチャンネルをオープンすることを指します。 |
HDP アプリの作成
Android HDP アプリの作成に必要な基本ステップは次のとおりです。
BluetoothHealthプロキシ オブジェクトへの参照を取得します。通常のヘッドセットや A2DP プロファイル端末と同様に、
BluetoothProfile.ServiceListenerとプロファイルの種類であるHEALTHを指定してgetProfileProxy()を呼び出し、プロファイル プロキシ オブジェクトとの接続を確立します。BluetoothHealthCallbackを作成して、Health Sink として機能するアプリ設定(BluetoothHealthAppConfiguration)を登録します。- ヘルス機器との接続を確立します。一部の端末は、接続を開始します。 そのような端末では、このステップは実行する必要がありません。
- ヘルス機器と正常に接続できたら、ファイル ディスクリプタを使用してヘルス機器に対して読み取り / 書き込みを行います。
受信したデータは、IEEE 11073-xxxxx 仕様を実装した Health Manager を使用して解釈する必要があります。
- 完了したら、Health チャンネルをクローズしてアプリの登録を解除します。チャンネルは、長時間使用されていない場合にもクローズされます。
これらのステップを示す完全なコード サンプルについては、Bluetooth HDP(Health Device Profile)をご覧ください。