【Java】Android端末とBluetoothデバイスを接続する

昨日、「【Java】Android端末でBluethoothデバイスを検出する方法」という記事を投稿しましたが、今日はその続きで、検出した Bluetooth デバイスと Android 端末を接続する方法についてです。
ただし、実はまだ動いていません…。

今回参考にさせていただいた記事はこちらから。

03.Bluetoothデバイスとの接続・切断の処理を作る
https://www.hiramine.com/programming/bluetoothcommunicator/03_connect_disconnect.html

AndroidからBluetooth機器にSPP (Serial Port Profile) で接続する|d.sunnyone.org
http://d.sunnyone.org/2013/09/androidbluetoothspp-serial-port-profile.html

 

実装にはまず、BluetoothService という Class を定義します。

static public class BluetoothService {
    // 定数(Bluetooth UUID)
    private static final UUID UUID_SPP = UUID.fromString( "00001101-0000-1000-8000-00805F9B34FB" );

    // 定数
    static final int MESSAGE_STATECHANGE    = 1;
    static final int STATE_NONE             = 0;
    static final int STATE_CONNECT_START    = 1;
    static final int STATE_CONNECT_FAILED   = 2;
    static final int STATE_CONNECTED        = 3;
    static final int STATE_CONNECTION_LOST  = 4;
    static final int STATE_DISCONNECT_START = 5;
    static final int STATE_DISCONNECTED     = 6;

    // メンバー変数
    private int              mState;
    private ConnectionThread mConnectionThread;
    private Handler          mHandler;

    // 接続時処理用のスレッド
    private class ConnectionThread extends Thread {
        private BluetoothSocket mBluetoothSocket;
        // コンストラクタ
        ConnectionThread(BluetoothDevice bluetoothdevice) {
            try {
//                    mBluetoothSocket = bluetoothdevice.createRfcommSocketToServiceRecord(UUID_SPP);
                mBluetoothSocket = bluetoothdevice.createInsecureRfcommSocketToServiceRecord(UUID_SPP);
            } catch( IOException e ) {
                Log.e( "BluetoothService", "failed : bluetoothdevice.createRfcommSocketToServiceRecord( UUID_SPP )", e );
            }
        }

        // 処理
        public void run() {
            while( STATE_DISCONNECTED != mState ) {
                switch( mState ) {
                    case STATE_NONE:
                        break;
                    case STATE_CONNECT_START:    // 接続開始
                        for (int i = 0; ; i++) {
                            try {
                                mBluetoothSocket.connect();
                            } catch (IOException ex) {
                                if (i < 5) {
                                    Log.d("BluetoothService","Failed to connect. Retrying: " + ex.toString());
                                    continue;
                                }
                                Log.d("BluetoothService", "Failed to connect: " + ex.toString());
                                setState(STATE_DISCONNECT_START);
                                return;
                            }
                            break;
                        }
                        // 接続成功
                        setState( STATE_CONNECTED );
                        break;
                    case STATE_CONNECT_FAILED:        // 接続失敗
                        Log.d("BluetoothService", "STATE_CONNECT_FAILED");
                        // 接続失敗時の処理の実体は、cancel()。
                        break;
                    case STATE_CONNECTED:        // 接続済み(Bluetoothデバイスから送信されるデータ受信)
                        break;
                    case STATE_CONNECTION_LOST:    // 接続ロスト
                        // 接続ロスト時の処理の実体は、cancel()。
                        break;
                    case STATE_DISCONNECT_START:    // 切断開始
                        // 切断開始時の処理の実体は、cancel()。
                        break;
                    case STATE_DISCONNECTED:    // 切断完了
                        // whileの条件式により、STATE_DISCONNECTEDの場合は、whileを抜けるので、このケース分岐は無意味。
                        break;
                }
            }
            synchronized( BluetoothService.this ) {    // 親クラスが保持する自スレッドオブジェクトの解放(自分自身の解放)
                mConnectionThread = null;
            }
        }

        // キャンセル(接続を終了する。ステータスをSTATE_DISCONNECTEDにすることによってスレッドも終了する)
        void cancel() {
            try {
                mBluetoothSocket.close();
            } catch( IOException e ) {
                Log.e( "BluetoothService", "Failed : mBluetoothSocket.close()", e );
            }
            setState( STATE_DISCONNECTED );
        }
    }

    // コンストラクタ
    BluetoothService(Context context, Handler handler, BluetoothDevice device) {
        mHandler = handler;
        mState = STATE_NONE;
        // 接続時処理用スレッドの作成と開始
        mConnectionThread = new ConnectionThread(device);
        mConnectionThread.start();
    }

    // ステータス設定
    private synchronized void setState( int state ) {
        mState = state;
        mHandler.obtainMessage( MESSAGE_STATECHANGE, state, -1 ).sendToTarget();
    }

    // 接続開始時の処理
    synchronized void connect() {
        if( STATE_NONE != mState ) {    // 1つのBluetoothServiceオブジェクトに対して、connect()は1回だけ呼べる。
            // 2回目以降の呼び出しは、処理しない。
            return;
        }
        // ステータス設定
        setState( STATE_CONNECT_START );
    }

    // 接続切断時の処理
    synchronized void disconnect() {
        if( STATE_CONNECTED != mState ) {    // 接続中以外は、処理しない。
            return;
        }
        // ステータス設定
        setState( STATE_DISCONNECT_START );
        mConnectionThread.cancel();
    }
}

この関数内で、BluetoothSocket の設定から接続時の処理などを指定します。

次に、BluetoothService から情報を取得するためのハンドラを定義します。

// Bluetoothサービスから情報を取得するハンドラ
@SuppressLint("HandlerLeak")
private final Handler mHandler = new Handler() {
    // ハンドルメッセージ
    // UIスレッドの処理なので、UI処理について、runOnUiThread対応は、不要。
    @Override
    public void handleMessage( Message msg ) {
        if (msg.what == BluetoothService.MESSAGE_STATECHANGE) {
            switch (msg.arg1) {
                case BluetoothService.STATE_NONE:            // 未接続
                    break;
                case BluetoothService.STATE_CONNECT_START:        // 接続開始
                    Log.d("MainActivity", "STATE_CONNECT_START");
                    break;
                case BluetoothService.STATE_CONNECT_FAILED:            // 接続失敗
                    Toast.makeText(MainActivity.this, "Failed to connect to the device.", Toast.LENGTH_SHORT).show();
                    Log.d("MainActivity", "STATE_CONNECT_FAILED");
                    break;
                case BluetoothService.STATE_CONNECTED:    // 接続完了
                    Log.d("MainActivity", "STATE_CONNECTED");
                    break;
                case BluetoothService.STATE_CONNECTION_LOST:            // 接続ロスト
                    //Toast.makeText( MainActivity.this, "Lost connection to the device.", Toast.LENGTH_SHORT ).show();
                    break;
                case BluetoothService.STATE_DISCONNECT_START:
                    break;
                case BluetoothService.STATE_DISCONNECTED:            // 切断完了
                    mBtAdapter = null;    // BluetoothServiceオブジェクトの解放
                    break;
            }
        }
    }
};

なお、こちらもサンプルコードからほぼそのままコピーしてきたものですが、ボタンの無効化などの一部不要な処理を削除しておりますので、ご注意ください。

そして、デバイスと接続/切断するための関数を定義します。

// 端末と接続
private void connectDevice(BluetoothDevice device) {
    Log.d("MainActivity","device.name : "+device.getName()+", device.address : "+device.getAddress());
    if( null != mBluetoothService ) {    // mBluetoothServiceがnullでないなら接続済みか、接続中。
        return;
    }
    // 接続
    BluetoothDevice connectDevice = mBtAdapter.getRemoteDevice(device.getAddress());
    mBluetoothService = new BluetoothService(this, mHandler, connectDevice);
    mBluetoothService.connect();
}

// 端末との接続を切断
private void disconnectDevice() {
    if( null == mBtAdapter ) {    // mBluetoothServiceがnullなら切断済みか、切断中。
        return;
    }
    // 切断
    mBluetoothService.disconnect();
    mBluetoothService = null;
}

なお、これらを追加すると恐らく変数などの定義でエラーが発生するため、onCreate よりも上の階層で下記を定義してください。

private BluetoothService mBluetoothService;

あとは任意のタイミングで、connectDevice(BluetoothDevice) を呼び出せば、接続処理が実行されます。
私は、デバイスが見つかったタイミングで実行しています。
そのタイミングでなら、デバイス情報も取得できますしね。

…が、こちらを実行すると、java.io.IOException: read failed, socket might closed or timeout, read ret: -1 というエラーが発生しました…。
ソケットが閉じているか、接続にかかる時間が長すぎてタイムアウトしているようですが、どう直せばいいのかまだわかっていません。
エラーメッセージで検索しているのですが、有益な記事にはたどり着けずにいます。
UUID が悪さをしているのかもしれませんので、このあたりから攻め直してみたいと思います!

 

以上、まだ完成していませんが、Android 端末と Bluetooth デバイスを接続する方法(途中)でした。
解決方法が分かったら、またまとめたいと思います。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG