Blog -
これで、その4ですがやっと接続後の処理への理解になります。
前回までに接続の確立まで行いましたが
個人的に注意点として
・接続した後に、有効範囲からどちらかが居なくなる。
・どちらかの電源が切れる。
いつ切れるか解らない処理も考えながら作らないと実用的にはならない。
まだまだ、先は長いですが今回は、接続以降にどの様な処理があるか理解します。 いつもの様に、Threadで実現します。 宣言部
private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; //中略 }
中で使うメンバーを定義します、見た事がある奴も居ます。コンストラクタですが、BluetoothSocketからStreamを取得します。
以降、Streamとして処理が出来ます。
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; }
次にrun()メソッドが実行されます。
先に、Handlerについてですが、ThreadからUIに対して処理が行えません
これは非同期処理中に直接ThreadからUIへ更新を掛ける為です。考えれば非同期処理からUIを触りたい処理ばかりなのですが、やり方があります。
それは、Handlerを使う方法です。Handlerは送られてきたメッセージを受け取り、処理します非同期処理から非同期にHandlerに渡して
HandlerはそれをUIへ更新します。(正確には、postかmessageを使ってBundleなどとやり取りをするみたいです)
他に非同期処理はAsynctaskなどもあります。 今回は、HandlerのMessageを利用してUIとのやり取りを行っています。
(他にも方法はありますがobtainMessageでにやり取りの効率が良いらしいです)
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();
対象のメソッドと引数は obtainMessage(int what, int arg1, int arg2, Object obj) ・MESSAGE_READは、Handlerで受け取った時に
どの処理に対してか識別します。・int bytesは第一引数・bufferは、InputStream(送られて来たデータ)
これを受け取って、UIへHandlerがMessageオブジェクトとして渡しています。 と話を戻しますが。
bytes = mmInStream.read(buffer);
のメソッドでストリームからデータが送られて来るまで待機します。
送られて来たら、UIへHandlerがMessageオブジェクトとして渡しています。
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) { } }
今回までで、接続からデータの受信(正確にはサーバ側でデータ書き込みは解説してませんが)まで理解出来たかと思います。
もう少し深く理解して、後の処理も考えたいと思っています。
※GoogleDevelopers チュートリアルを使用しています。
次にクライアント側へ理解を広げたいと思います。
クライアント側はどうなっているのか
【クライアントとしての接続】
クライアントとしては、接続相手を探したおく必要があります。
接続相手は、発見可能な状態であってBluetoothServerSocketがリスニング状態である必要があります。
ここでは、相手を見つけた後の処理について書きます。
この処理も、Threadで実現するべきです。まず定義から
private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; //中略 }
BluetoothSocketとBluetoothDeviceをメンバーに持たせています。
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(UUID); } catch (IOException e) { } mmSocket = tmp; }
コンストラクタでは、BluetoothSocketを初期化してBluetoothDeviceを受け取ります。
BluetoothDeviceからcreateRfcommSocketToServiceRecord(MY_UUID)を発行する事で BluetoothSocketが相手のBluetoothServerSocketから戻されます。
その2で説明したBluetoothServerSocket開始時に指定したMY_UUIDと同じ事を前提にします。
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
成功すれば、BluetoothSocketが返されます。続いて、run()メソッドが開始されます。
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); }
cancelDiscovery()を最初に行っていますが、Discovery()(接続相手の検索)中である事が予測できる為接続前に中止します、
BluetoothAdapterは一つであればどのインスタンスからでもキャンセル出来ます。 その後、取得したBluetoothSocketに対してconnect()を行います。
Thread処理のキャンセルに対応して処理を作っておきます。
/** Will cancel an in-progress connection, and close the socket */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } }
これで、サーバ、クライアント両方の接続までは理解出来たかと思います。
なぜ、Threadなのか?に関しては、後日時間が有れば解説します。
※GoogleDevelopers チュートリアルを使用しています。
前回は、設計に際しての概要まで纏めましたが 今回はちょっと具体的に
【サーバとしての接続】
private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; //中略 }
AcceptThreadというThreadを作って、そのコンストラクタで
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; }
BluetoothServerSocketをの有無を確認して無ければ(無いというのは、接続中か接続後か)
BluetoothServerSocketを用意する、Threadなのでrun()メソッドが開始される。
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; } } }
run()の中では、BluetoothSocketを初期化してaccept()を発行しリスニングを開始します。
クライアントがBluetoothSocketを要求するとBluetoothSocketがsocketへソケットを用意します。
socket(接続以降)の処理を開始して、BluetoothSocketをclose()して終了します。 また、処理を中断した時の処理も記述します。
public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { } }
これで、サーバソケットでの待機が理解出来ました。
注意点として、accept() が BluetoothSocket を返すときは、ソケットがすでに接続済みでなので
connect() メソッドを呼び出す必要はありません ( クライアントサイドでしている想定 )
※GoogleDevelopers チュートリアルを使用しています。
ブログにソースコードなどを貼り付ける時にハイライトや色分けされていないと見づらいし萎えてしまうので google-code-prettifyを導入しました。
package jp.android.helloworld;
import android.app.Activity;
import android.os.Bundle;
public class HelloWorld extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
package jp.android.helloworld;
import android.app.Activity;
import android.os.Bundle;
public class HelloWorld extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
こんな感じ、ん?動いてるのかな今後、試行錯誤 → 下が正解、間違ってたので修正orz
ただ、余り文献が無いという事で、調べた事をメモ的に記載します。
(基本的に2.1以降を対象とします)
【Android内で抽象化されたBluetoothのクラス】
BluetoothAdapter・・・ハードその物を表します。
BluetoothDevice・・・リモートのデバイスを表します、いわゆる相手。
BluetoothSocket・・・相手との通信にStreamを利用する場合のソケット。
BluetoothServerSocket・・・リクエストを待ち受けしているサーバソケット
BluetoothClass・・・Bluetoothのプロパティセット(読み込み専用)
大きくはこんな所、ここでちょっと解りににくいポイントのまとめ
・サーバは待ち受けるリスナー。
・クライアントは、サーバへ接続に来る。
・接続には一般的にSocketを利用する。
【パーミッション】
Bluetoothを利用するには
BLUETOOTH
BLUETOOTH_ADMIN
が必要になる。
【発見機能】
通常、BluetoothはON/OFFですが最近ではむやみに発見されない様に
発見させるかさせないかのON/OFFが機能として実装されています。
【デバイスの接続】
サーバサイドとクライアントサイドの両方の機構で実装が必要になる。
(まあ当然ですが)
同一の RFCOMM チャネル上で、それぞれが接続済みの BluetoothSocket
を受け取ったときに接続されたとみなされます。
ポイント
・ペアリングされたデバイスの場合はこの限りではない
・ペアリングされていないデバイスは検索してBluetoothDeviceを得る必要がある
(そのMACアドレス)
【設計のポイント】
2つの端末で通信をする場合に、どちらかがサーバで一方がクライアントの動きをする
と言う感じになる、先にサーバでSocketを獲得した方がサーバとして振る舞い接続を確立する。
サーチ
発見
BluetoothDeviceを獲得
Sockeを要求、取得
通信開始
と同時に
発見可能へ
サーバソケットリスニング開始(accept())
接続要求受領
Socketを提供
*サーバソケットリスニング停止(破棄)
通信開始
通信終了
発見可能へ
サーバソケットリスニング開始
とこうなる訳ですが、重要なのは※の部分
サーバソケットを停止(破棄していますが)RFCOMMでは同時通信はありません(チャネルあたりひとつ)ですので
接続を待つ必要性がなくなります。よって高負荷の処理は破棄します。
破棄してもSocketは破棄されない為、通信は可能です。
また、acceptはブロックされた呼び出しである為、AndroidのUI(シングル)では高負荷ですのでActivityのUIで
実行するのは現実的ではありません、別スレッドで処理します。
その2へ続く