본문 바로가기
Programming/Android Java

블루투스(Bluetoot)통신, SPP

by 개Foot/Dog발?! 2014. 8. 28.

URL : http://dlucky.tistory.com/201



.....


블루투스 API를 사용하면 다음과 같은 작업을 할 수 있다.

- 다른 블루투스 디바이스 검색

- 페어링 된 블루투스 디바이스를 위한 로컬 블루투스 아답터 퀘리

- RFCOMM 채널 설정

- SDP(Service Discovery Protocol)을 통한 다른 디바이스와의 커넥션

- 양방향 데이터 전송

- 복수 커넥션 관리


- 기초


.....


다음은 블루투스 연결을 만드는데 필요한 클래스들의 요약이다.

- BluetoothAdapter - 로컬 블루투스 아답터 하드웨어를 나타낸다. BluetoothAdapter는 모든 블루투스를 통한 상호작용의 엔트리포인트이다. 이 객체를 사용해서 다른 블루투스 디바이스 찾기, 페어링 된 디바이스 퀘리, 알려진 MAC address를 사용해 BluetoothDevice 인스턴스 얻기, 다른 디바이스에서 부터의 통신 요구를 기다리기 위한BluetoothServerSocket 만들기를 할 수 있다.

- BluetoothDevice - 상대방의 블루투스 디바이스를 나타낸다. 이 객체를 사용하면 BluetoothSocket을 통해 상대방 디바이스와 커넥션을 요구하거나 이름, 주소, 클래스, 페어링 상태등의 정보를 퀘리할 수 있다.

- BluetoothSocket - 블루투스 소켓을 위한 인터페이스를 나타낸다. 어플리케이션이 InputStream과 OutputStream을 사용해서 다른 블루투스 디바이스와 데이터 교환을 할 수 있는 연결 포인트이다.

- BluetoothServerSocket – Incoming 리퀘스트를 위해 listen하고 있는 오픈된 서버소켓(TCP ServerSocket과 유사)을 나타낸다. 두대의 안드로이드 디바이스를 연결하기 위해 한쪽의 디바이스는 이 클래스를 사용해서 서버소켓을 오픈해야만 한다. 원격 블루투스 디바이스가 디바이스에 커넥션 리퀘스트를 할 때 BluetoothServerSocket은 커넥션이 연결되면 연결된 BluetoothSocket을 리턴해준다.

- BluetoothClass - 블루투스 디바이스의 일반적 특성과 기능을 나타낸다. 이 클래스는 디바이스의 디바이스 클래스

와 서비스를 정의하는 읽기 전용 속성의 집합이다.


- 블루투스 퍼미션

어플리케이션에서 블루투스 기능을 사용하려면 최소한 BLUETOOTH와 BLUETOOTH_ADMIN 둘중에 하나의 블루투스 퍼미션을 선언해줘야 한다. 커넥션 요구, 커넥션 accept, 데이터 전송등의 블루투스 통신을 하기 위해서는 BLUETOOTH 퍼미션이 필요하다.

디바이스 discovery를 시작하거나 블루투스 설정을 조작하려면 BLUETOOTH_ADMIN 퍼미션이 필요하다.

BLUETOOTH_ADMIN 퍼미션을 사용하려면 BLUETOOTH 퍼미션도 꼭 있어야만 한다. 매니페스트 파일에 블루투스 퍼미션을 선언해준다.


<uses-permission android:name="android.permission.BLUETOOTH" />

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> 


- 블루투스 셋업


.....


1.BluetoothAdapter 를 얻는다.

.....


** 추가 comment : Android 4.3이상에서는 Bluetooth LE를 지원하면서 Framework에 BluetoothAdapter가 변경되고 BluetoothManager가 추가되어 이전 버전은 첫번째 코드로 작업하면 되지만, 4.3이상에서는 두번째와 같은 코드로 작업을 해야 한다.

BluetoothAdapter mBTAdapter = BluetoothAdapter.getDefaultAdapter();

if (mBTAdapter == null) {

// device does not support Bluetooth

} 

BluetoothAdapter mBluetoothAdapter = null;


// Get local Bluetooth adapter

if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2){

BluetoothManager mBtManager = (BluetoothManager) this.getSystemService(Context.BLUETOOTH_SERVICE);

if (mBtManager == null) {

Log.e(TAG, "Unable to initialize BluetoothManager from BluetoothService on Android 4.3(SDK v18)");

}

else {//under Android 4.3

mBluetoothAdapter = mBtManager.getAdapter();

}

}

else {

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

}


2.블루투스 활성화

.....


** 추가 comment : 첫번째나 두번째 모두 가능하지만, Google에서는 아래와 같은 이유로 가급적 사용자의 동의하에 하거나 사용자가 제어하는 방향으로 유도할 것을 권고하고 있다.

If (!mBTAdapter.isEnabled()) {

Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT) ;

} 

if (mBluetoothAdapter.isEnabled()) {

mBluetoothAdapter.disable();

}

else {

mBluetoothAdapter.enable();

}

 
Bluetooth should never be enabled without direct user consent. If you want to turn on Bluetooth in order to create a wireless connection, you should use the ACTION_REQUEST_ENABLE Intent, which will raise a dialog that requests user permission to turn on Bluetooth. The enable() method is provided only for applications that include a user interface for changing system settings, such as a "power manager" app.


.....


블루투스 활성화가 성공하면 액티비티는 onActivityResult() 콜백에서 RESULT_OK를 리턴받게 된다. 블루투스가 에러로인해 (또는 사용자가 “No”를 선택해서) 활성화되지 못하면 RESULT_CANCELED가 리턴된다. 옵션으로 블루투스 상태가 변경될 때 마다 시스템이 브로드캐스하는 ACTION_STATE_CHANGED 인텐트를 listen하도록 할 수도 있다


- 디바이스 검색


블루투스 활성화가 성공하면 액티비티는 onActivityResult() 콜백에서 RESULT_OK를 리턴받게 된다. 블루투스가 에러로인해 (또는 사용자가 “No”를 선택해서) 활성화되지 못하면 RESULT_CANCELED가 리턴된다. 옵션으로 블루투스 상태가 변경될 때 마다 시스템이 브로드캐스하는 ACTION_STATE_CHANGED 인텐트를 listen하도록 할 수도 있다


- 디바이스 검색


.....


디바이스 discovery는 주변의 활성화 된 블루투스 디바이스를 찾고 각각에 대한 정보를 요구하는 검색 단계이다. 하지만 통신가능 범위에 들어있는 블루투스 디바이스라 해도 현재 discoverable 하도록 활성화 되어 있어야만 discovery 요구에 응답한다. 디바이스가 discoverable 상태인 경우 discovery 요구에 디바이스 이름, 클래스, MAC 주소같은 정보를 공유함

으로서 응답한다. 이 정보를 사용해서 discovery를 수행한 디바이스는 발견된 디바이스에 커넥션을 시작하도록 선택할 수있다.

일단 원격 디바이스와 처음으로 연결이 이루어지면 자동으로 사용자에게 페어링을 할 것인가 물어보게 된다. 디바이스 페어링이 이루어지면 상대 디바이스에 대한 기본 정보(디아비스 이름, 클래스, MAC 주소 등)가 저장되고 그 내용은 블루투스 API를 통해 읽을 수 있게 된다. 이미 알고 있는 원격디바이스의 MAC 주소를 사용하면 아무때나 (물론 해당 디바이스가

통신 가능범위에 있다는 가정 하에) discovery를 수행할 필요 없이 바로 커넥션 과정을 시작할 수 있다.

페어링과 연결된것의 차이점은 잘 알고 있어야 한다. 페어링은 두 디바이스가 각자 상대방의 존재를 알고 있고 인증과정에 사용할 link-key를 공유하고 있어 서로간에 암호화 된 연결을 설정할 수 있다는걸 의미한다. 연결된것은 디바이스가 현재RFCOMM 채널을 공유하고 있어 서로 데이터를 전송할 수 있는 상태를 의미한다.

현재 안드로이드 블루투스 API는 RFCOMM 커넥션을 설정하기 전에 디바이스가 페어링 되어야만 한다. (블루투스 API에서 암호화된 커넥션을 시작하려고 할 때 페어링이 자동을 이루어진다.)


.....


- 페어링 된 디바이스 퀘리


디바이스 discovery를 수행하기 전에 원하는 디바이스가 이미 페어링 되어 있는가 확인해 볼 필요가 있다. 확인하기 위해

서 getBondedDevices()를 호출하면 된다.


.....


Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();

if (pairedDevices.size() <> 0) {

for (BluetoothDevice device : pairedDevices) {

mArrayAdapter.add(device.getName() + “\n” + device.getAddress());

}

} 


.....


- 디바이스 discovery


디바이스 discovery를 시작하려면 startDiscovery()를 호출하면 된다. 이 과정은 비동기식이라 메소드를 호출하면 discovery가 성공적으로 시작되었나 결과를 알려주는 boolean값을 곧바로 돌려준다. Discovery과정은 보통 12초간의 inquiry scan후 발견된 각 디바이스에 대해 이름을 가져오기 위한 page scan으로 이루어진다.

어플리케이션은 각 발견된 디바이스에 대한 정보를 받기 위해 ACTION_FOUND 인텐트를 위한 BroadcastReceiver를 등록해야만 한다. 각 디바이스마다 시스템이 ACTION_FOUND 인텐트를 브로드캐스트 한다. 이 인텐트는 각각 BluetoothDevice와 BluetoothClass가 들어있는 EXTRA_DEVICE와 EXTRA_CLASS 필드를 전달한다


.....


final BroadcastReceiver mReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

String action = intent.getAction();

if (BluetoothDevice.ACTION_FOUND.equals(action)) {

BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

mArrayAdapter.add(device.getName() + “\n” + device.getAddress());

}

}

};


BroadcastReceiverIntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);

registerReceiver(mReceiver, filter);



커넥션을 시작하기 위해 BluetoothDevice 객체에서 필요한 정보는 MAC 주소뿐이다


.....


주의: 디바이스 discovery를 수행하는건 블루투스 아답터에게 매우 부담이 큰 작업으로 매우 많은 리소스를 요구한다. 커넥션 할 디바이스를 찾았다면 커넥션을 시작하려고 시도하기 전에 cancelDiscovery()를 호출해서 discovery를 멈춰야 한다. 또한 이미 다른 디바이스와 커넥션 되어 있으면 discovery과정동안 대역폭이 활 떨어질수도 있기 때문에 커넥션 된상태에서는 discovery를 하지 않아야 한다.


- Discoverable 활성화

다른 디바이스가 자신의 디바이스를 검색할 수 있도록 해 주려면 startActivityForResult(Intent, int)에 ACTION_REQUEST_DISCOVERABLE 액션 인텐트를 넣어 호출해주면 된다. 이 메소드를 호출하면 어플리케이션을 멈추지 않고 시스템 설정을 통해 discoverable 모드를 활성화 하도록 요청한다. 기본적으로 디바이스는 120초동안 discoverable 모드로 있게 된다. EXTRA_DISCOVERABLE_DURATION 인텐트 extra를 추가해서 이 시간을 바꿔줄 수 있다. (최대 300초)


Intent discoverableIntent = new Intent(BluetoothAdpater.ACTION_REQUEST_DISCOVERABLE);

discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);

startActivity(discoverableIntent); 


 다이얼로그가 떠서 사용자에게 디바이스를 discoverable 상태로 만들도록 허가할 것인지 묻는다. “Yes”를 선택하면 디바이스는 정해진 시간동안 discoverable상태가 된다. 액티비티는 result code에 디바이스가 discoverable되는 시간값이 들어가서 onActivityResult() 콜백을 호출받게 된다. 사용자가 “No”를 선택하거나 에러가 발생하면 result code는 Activity.RESULT_CANCELLED가 된다.

디바이스는 discoverable 시간동안 아무 반응이 없이 조용히 있는다. 만일 discoverable모드가 변경될 때 통보를 받고 싶으면 ACTION_SCAN_MODE_CHANGED 인텐트에 대한 BroadcastReceiver를 등록할 수 있다. 이 인텐트에는 각각 이전 스캔모드와 변경된 새 스캔모드가 들어있는 EXTRA_PREVIOUS_SCAN_MODEEXTRA_SCAN_MODE라는 extra 필드를 가지고 있다. 각 필드에 들어갈 수 있는 값은 SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE, SCAN_MODE_NONE으로 각각 discoverable 모드, discoverable은 아니지만 커넥션을 받아들일 수는 있는 모드, discoverable도 아니고 커넥션도 받아들일 수 없는 모드를 나타낸다.

원격 디바이스와 커넥션을 시작하고 싶은 경우는 자신의 디바이스를 discoverable모드로 만들 필요는 없다. 원격 디바이스가 커넥션을 시작하기 전에 디바이스를 발견해야만 하기 때문에 내 디바이스의 discoverable 모드를 활성화 시키는건 어플리케이션이 서버소켓을 사용해서 incoming 연결을 accept할 때만 필요하다.


- 디바이스 커넥션

두 디바이스에서 실행되는 어플리케이션간에 커넥션을 만들기 위해서는 서버쪽과 클라이언트쪽 메카니즘을 모두 구현해줘야만 한다. 한 디바이스는 서버소켓을 열어줘야 하고 다른 디바이스가 서버 디바이스의 MAC 주소를 사용해서 커넥션을 시작해야만 하기 때문이다. 서버와 클라이언트는 같은 RFCOMM 채널에 각각 커넥션 된 BluetoothSocket을 가지고 있을때 서로 커넥트 된 것으로 간주된다. 이 지점에서 각 디바이스는 입, 출력 스트림을 얻어 데이터 전송을 시작할 수 있다


.....


한가지 구현 테크닉은 두 디바이스를 모두 서버로 동작하도록 하기 위해 서버소켓을 열고 커넥션을 기다리는 것이다. 그러면 어느 디바이스건 클라이언트로서 상대 디바이스로 커넥션을 시작할 수 있다. 다른 방법으로는 한 디바이스는 명시적으로 서버로 지정해 서버소켓을 열고 커넥션을 기다리고 다른 디바이스는 단순히 클라이언트로 커넥션을 시작할 수 있다.


주) 두 디바이스가 미리 페어링 되어 있지 않으면 안드로이드 프레임웍은 자동으로 페어링을 요구하는 다이얼로그를 띄워준다. 그러므로 디바이스를 커넥트 하려고 할 때 어플리케이션은 디바이스가 미리 페어링 되어 있는지 여부를 걱정할 필요가 없다. RFCOMM 커넥션 시도는 사용자가 성공적으로 페어링을 마치거나 페어링을 거부하거나 또는 어떤 이유로건 페어링이 실패할 때 까지 블럭된다.


서버로 동작

두 디바이스를 커넥트하려고 할 때 하나의 디바이스는 BluetoothServerSocket을 열어 서버로 동작해야만 한다. 서버소켓의 목적은 incoming 커넥션 요구를 기다리다 accept되면 커넥션 된 BluetoothSocket을 제공해 주는 것이다.


.....


1.listenUsingRfcommWithServiceRecord(String, UUID)를 호출해서 BluetoothServerSocket을 얻어온다.

스트링은 서비스에 대한 식별할 수 있는 이름으로 시스템이 디바이스의 새 SDP 데이터베이스 엔트리에 자동으로 그 이름을 기록한다. UUID 또한 SDP엔트리에 포함되어 클라이언트와 커넥션 agreement를 위한 기초가 된다. 즉 클라이언트가 디바이스와 커넥션하려고 시도할 때 커넥션하길 원하는 서비스를 유일하게 식별하는 UUID를 제공한다. 커넥션이 이뤄지기 위해서는 이 UUID가 일치해야만 한다.


2.accept()를 호출해서 커넥션 요구를 listen하기 시작한다.

이 메소드는 블럭킹 호출이다. 커넥션이 accept되거나 익셉션이 발생해야만 리턴된다. 리모트 디바이스가 listen하고 있는 서버소켓에 등록한 UUID와 일치하는 커넥션 요구에만 연결이 만들어진다. 성공하면 accept()는 커넥션 된 BluetoothSocket을 리턴한다.


3.더 이상의 추가 커넥션이 필요하지 않으면 close()를 호출한다.

이 메소드를 호출하면 서버소켓과 관련된 리소스를 release한다. 하지만 accept()가 리턴한 커넥션 된 BluetoothSocket은 닫지 않는다. TCP/IP와 달리 RFCOMM은 클라이언트에서 한번에 하나의 커넥션만 허용하기 때문에 대부분의 경우에 커넥션이 만들어지면 곧바로 BluetoothServerSocket을 close()하는게 합리적이다.

accept()는 블럭킹 메소드라 어플리케이션의 다른 동작을 막기 때문에 메인 액티비티 UI 스레드에서 호출하면 안된다. 일반적으로 새로운 스레드에서 BluetoothSocket이나 BluetoothServerSocket에 관련된 모든 작업을 처리하는게 합리적이다.

다른 스레드에서 BluetoothServerSocket의 accept() 같이 블럭킹 된 것을 취소하고 바로 리턴하도록 하려면 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 = mAdapter.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) { }

}

} 


이 예제에서 한개의 incoming 커넥션만 필요하기 때문에 커넥션이 accept되고 BluetoothSocket이 얻어지자 마자 어플리

케이션은 얻은 BluetoothSocket을 별도의 스레드로 보낸 다음 BluetoothServerSocket을 닫고 루프를 빠져나온다.

accept()가 BluetoothSocket을 리턴할 때 소켓은 이미 커넥션 되어 있기 때문에 따로 connect()를 호출할 필요는 없다.

manageConnectedSocket()은 어플리케이션에서 데이터 전송을 위한 스레드를 시작하는 fictional 메소드이다.

일반적으로 incoming 커넥션을 listen하는게 끝나면 곧바로 BluetoothServerSocket을 닫아준다. 이 예제에서도 BluetoothSocket이 얻어지자 마자 close()를 호출했다. 또한 listen하고 있는 서버소켓을 멈출 필요가 있을 때 private

BluetoothSocket을 닫을 수 있는 public 메소드를 스레드에서 제공하기도 한다.


클라이언트로 동작

원격 디바이스와 커넥션을 시작하려면 우선 원격 디바이스를 나타내는 BluetoothDevice 객체를 얻어야만 한다. 그리고 나면 BluetoothDevice를 사용해서 BluetoothSocket을 얻어 커넥션을 시작한다.



.....


1.BluetoothDevice를 사용해서 createRfcom m SocketT oServiceRecord(UUID)를 호출해서 BluetoothSocket을 얻는다.

이 호출은 BluetoothDevice에 연결하는 BluetoothSocket을 초기화한다


.....


2.connect()를 호출해서 연결을 시작한다.

시스템은 UUID를 매치하기 위해 원격 디바이스 SDP lookup을 수행한다. Lookup이 성공하고 원격 디바이스가 커넥션을 accept하면 연결동안 사용할 RFCOMM채널을 공유하고 connect()가 리턴한다. 이 메소드는 블럭킹 호출이다. 어떤 이유로건 커넥션이 실패하거나 connect() 메소드가 time out (약 12초)이 되면 exception을 발생한다.

connect()는 블럭킹 호출이기 때문에 이 커넥션 절차는 언제나 메인 액티비티 스레드와 독립된 별개의 스레드에서 수행되어야만 한다.

주: connect()를 호출할 때 디바이스는 언제나 디바이스 discovery를 수행하고 있지 않는지 확인해야만 한다. Discovery 가 진행중이면 커넥션 시도는 확연히 느려져서 실패할 가능성이 커진다.


예제)

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

mAdapter.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) { }

}

} 


.....


BluetoothSocket이 끝나면 clean up을 위해 언제나 close()를 호출해줘야 한다. 이 메소드를 호출해 줌으로서 곧바로 커넥

션 된 소켓을 닫고 내부 리소스를 clean up 하게 된다.


- 연결 관리

두 디바이스를 성공적으로 커넥션하게 되면 각 디바이스는 커넥션 된 BluetoothSocket을 가지게 된다. 이 소켓을 통해 디

바이스간에 데이터를 교환할 수 있게 된다. BluetoothSocket을 사용해서 임의의 데이터를 전송하기 위한 일반적 절차는 매

우 간단하다.

1.각각 getInputStream()과 getOutputStream()을 사용해 소켓을 통한 전송을 처리할 InputStream과 OutputStream을 얻는다.

2.read(byte[])와 write(byte[])를 사용해서 데이터를 읽고 쓴다.


물론 implementation을 위해 고려해야 할 세부사항들이 있다. 먼저 무엇보다 모든 읽고 쓰기를 위한 별도의 스레드를 사용

해야 한다. 이건 read(byte[])와 write(byte[])는 모두 블럭킹 호출이기 때문에 매우 중요하다


.....


write(byte[])는 일반적으로는 블럭되지 않지만 원격 디바이스가 충분히 빠르게 read(byte[])를 호출하지 않아 버퍼가 꽉 차는 경우 플로우 컨트롤을 위해 블럭될수도 있다. 그러므로 스레드의 메인 루프는 InputStream으로부터 읽기 전용으로 사용되어야 한다


예제)

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) { }

}

} 


.....


스레드의 cancel() 메소드는 아무때나 BluetoothSocket을 닫아 connection을 멈출 수 있기 때문에 중요하다. 이 메소드는

블루투스 connection 사용이 끝나면 언제나 호출되어야 한다.