lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

블루투스

Android 플랫폼은 블루투스 네트워크 스택에 대한 지원을 포함하므로 기기가 다른 블루투스 기기와 데이터를 무선으로 교환할 수 있습니다. 애플리케이션 프레임워크는 Android Bluetooth API를 통해 블루투스 기능에 대한 액세스 권한을 제공합니다. 애플리케이션은 이러한 API를 통해 다른 Bluetooth 기기에 무선으로 연결하여 지점 간 및 다중 지점 무선 기능을 활성화할 수 있습니다.

Android 애플리케이션은 Bluetooth API를 사용하여 다음 작업을 수행할 수 있습니다.

  • 다른 블루투스 기기 스캔
  • 페어링된 블루투스 기기에 대한 로컬 블루투스 어댑터 쿼리
  • RFCOMM 채널 설정
  • 서비스 검색을 통해 다른 기기에 연결
  • 기기 간 데이터 전송 및 수신
  • 다중 연결 관리

이 문서는 클래식 블루투스를 사용하는 방법에 대해 설명합니다. 클래식 블루투스는 Android 기기 간 스트리밍 및 통신과 같이 배터리를 많이 사용하는 작업에 적합한 선택입니다. 전원 요구사항이 낮은 블루투스 기기를 위해 Android 4.3(API 레벨 18)은 블루투스 저전력용 API 지원을 소개합니다. 자세한 내용은 블루투스 저전력을 참조하세요.

기본 사항

이 문서는 Android Bluetooth API를 사용하여 블루투스 설정, 로컬 영역에서 페어링되었거나 사용 가능한 기기 찾기, 기기 연결, 기기 간 데이터 전송 등 블루투스 통신에 필요한 4가지 주요 작업을 수행하는 방법에 대해 설명합니다.

모든 Bluetooth API는 android.bluetooth 패키지에서 구할 수 있습니다. 다음은 블루투스 연결을 설정하는 데 필요한 클래스 및 인터페이스에 대한 간략한 설명입니다.

BluetoothAdapter
로컬 블루투스 어댑터(블루투스 송수신 장치)를 나타냅니다. BluetoothAdapter는 블루투스 상호작용에 대한 진입점입니다. 이를 사용하여 다른 블루투스 기기를 검색하고 연결된(페어링된) 기기 목록을 쿼리하고 알려진 MAC 주소로 BluetoothDevice를 인스턴스화하고, 다른 기기로부터 통신을 수신 대기하는 BluetoothServerSocket을 만들 수 있습니다.
BluetoothDevice
원격 블루투스 기기를 나타냅니다. 이를 사용하여 BluetoothSocket을 통해 원격 기기와의 연결을 요청하거나 이름, 주소, 클래스 및 연결 상태와 같은 기기 정보를 쿼리합니다.
BluetoothSocket
블루투스 소켓(TCP Socket과 유사함)에 대한 인터페이스를 나타냅니다. 이는 InputStream 및 OutputStream을 통해 애플리케이션이 다른 블루투스 기기와 데이터를 교환할 수 있게 허용하는 연결 지점입니다.
BluetoothServerSocket
들어오는 요청을 수신 대기하는 열린 서버 소켓(TCP ServerSocket과 유사함)을 나타냅니다. 두 대의 Android 기기를 연결하려면 한 기기가 이 클래스를 사용하여 서버 소켓을 열어야 합니다. 원격 블루투스 기기가 이 기기에 연결 요청을 하는 경우 연결이 수락될 때 BluetoothServerSocket이 연결된 BluetoothSocket을 반환합니다.
BluetoothClass
블루투스 기기의 일반적인 특징 및 기능에 대해 설명합니다. 이는 주요 및 보조 기기 클래스와 기기 서비스를 정의하는 읽기 전용 속성 집합입니다. 그러나 이는 기기가 제공하는 모든 Bluetooth 프로필 및 서비스를 명확하게 설명하지 않지만 기기 유형에 대한 유용한 힌트입니다.
BluetoothProfile
Bluetooth 프로필을 나타내는 인터페이스입니다. Bluetooth 프로필은 기기 간 블루투스 기반 통신에 대한 무선 인터페이스 사양입니다. 일례로 Hands-Free 프로필이 있습니다. 프로필에 대한 자세한 내용은 프로필 작업을 참조하세요.
BluetoothHeadset
블루투스 헤드셋이 휴대폰과 함께 사용할 수 있도록 지원합니다. 이는 블루투스 헤드셋 및 핸즈프리(v1.5) 프로필을 모두 포함합니다.
BluetoothA2dp
블루투스 연결을 통해 고품질 오디오가 기기 간 스트리밍할 수 있는 방법을 정의합니다. "A2DP"는 Advanced Audio Distribution Profile(고급 오디오 배포 프로필)의 약자입니다.
BluetoothHealth
블루투스 서비스를 제어하는 의료 기기 프로필 프록시를 나타냅니다.
BluetoothHealthCallback
BluetoothHealth 콜백을 구현하는 데 사용되는 추상 클래스입니다. 애플리케이션 등록 상태 및 블루투스 채널 상태의 변경에 대한 업데이트를 받으려면 이 클래스를 확장하고 콜백 메서드를 구현해야 합니다.
BluetoothHealthAppConfiguration
원력 블루투스 의료 기기와 통신하기 위해 블루투스 의료 타사 애플리케이션이 등록하는 애플리케이션 구성을 나타냅니다.
BluetoothProfile.ServiceListener
서비스(특정 프로필을 실행하는 내부 서비스)와 연결하거나 연결을 끊을 때 BluetoothProfile IPC 클라이언트에 알리는 인터페이스입니다.

블루투스 권한

애플리케이션에서 블루투스 기능을 사용하려면 블루투스 권한 BLUETOOTH를 선언해야 합니다. 연결 요청, 연결 수락 및 데이터 전송과 같은 블루투스 통신을 수행하려면 이 권한이 있어야 합니다.

앱이 기기 검색을 시작하거나 블루투스 설정을 조작하려면 BLUETOOTH_ADMIN 권한도 선언해야 합니다. 대부분의 애플리케이션은 로컬 블루투스 기기를 검색하는 기능 전용으로 이 권한이 필요합니다. 애플리케이션이 사용자 요청 시 블루투스 설정을 수정하는 "파워 관리자"가 아닌 경우 이 권한이 부여하는 다른 기능은 사용해서는 안 됩니다. 참고: BLUETOOTH_ADMIN 권한을 사용하는 경우 BLUETOOTH 권한도 있어야 합니다.

애플리케이션 매니페스트 파일에 블루투스 권한을 선언합니다. 예:

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

애플리케이션 권한 선언에 대한 자세한 내용은 <uses-permission> 참조를 읽어보세요.

블루투스 설정

그림 1: 블루투스 활성화 대화상자

블루투스를 사용하여 통신하려면 블루투스가 기기에서 지원되는지 확인하고, 지원되는 경우 활성화해야 합니다.

블루투스가 지원되지 않은 경우 블루투스 기능을 비활성화해야 합니다. 지원되는 블루투스가 비활성화된 경우 개발자는 사용자가 애플리케이션을 떠나지 않고 블루투스를 활성화하도록 요청할 수 있습니다. 이 설정은 BluetoothAdapter를 사용하여 2단계로 수행됩니다.

  1. BluetoothAdapter 가져오기

    모든 블루투스 액티비티를 위해 BluetoothAdapter가 필요합니다. BluetoothAdapter를 가져오려면 정적 getDefaultAdapter() 메서드를 호출합니다. 그러면 기기의 자체 블루투스 어댑터(블루투스 송수신 장치)를 나타내는 BluetoothAdapter가 반환됩니다. 전체 시스템에 대한 단일 블루투스 어댑터가 있고 애플리케이션이 해당 객체를 사용하여 상호작용할 수 있습니다. getDefaultAdapter()가 null을 반환하는 경우 기기는 블루투스를 지원하지 않습니다. 예:

    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        // Device does not support Bluetooth
    }
    
  2. 블루투스 활성화

    이제 블루투스를 활성화해야 합니다. isEnabled()를 호출하여 블루투스가 현재 활성화되었는지 확인합니다. 이 메서드가 false를 반환하는 경우 블루투스는 비활성화됩니다. 블루투스 활성화를 요청하려면 ACTION_REQUEST_ENABLE 작업 인텐트를 사용하여 startActivityForResult()를 호출합니다. 그러면 (애플리케이션을 중지하지 않고) 시스템 설정을 통한 블루투스 활성화 요청이 발급됩니다. 예:

    if (!mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }
    

    그림 1과 같이 블루투스를 활성화하기 위해 사용자 권한을 요청하는 대화상자가 표시됩니다. 사용자가 "Yes"를 선택하면 시스템이 블루투스를 활성화하기 시작하고 해당 프로세스가 완료(또는 실패)하면 포커스가 애플리케이션으로 돌아갑니다.

    startActivityForResult()로 전달된 REQUEST_ENABLE_BT 상수는 시스템이 requestCode 매개변수로서 onActivityResult() 구현에서 개발자에게 다시 전달하는 지역적으로 정의된 정수(0보다 커야 함)입니다.

    블루투스 활성화에 성공하면 액티비티가 onActivityResult() 콜백에서 RESULT_OK 결과 코드를 수신합니다. 오류 때문에 블루투스를 활성화하지 못한 경우(또는 사용자가 "No"를 선택한 경우) 결과 코드는 RESULT_CANCELED입니다.

블루투스 상태가 변경될 때마다 시스템이 브로드캐스트하는 ACTION_STATE_CHANGED 브로드캐스트 인텐트를 애플리케이션이 수신 대기할 수도 있습니다. 이 브로드캐스트는 새로운 블루투스 상태와 이전 블루투스 상태를 각각 포함하는 추가 필드 EXTRA_STATEEXTRA_PREVIOUS_STATE을 포함합니다. 이 추가 필드에 가능한 값은 STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFFSTATE_OFF입니다. 이 브로드캐스트를 수신 대기하는 것은 앱 실행 중에 블루투스 상태 변경을 감지하는 데 유용할 수 있습니다.

팁: 검색 기능을 활성화하면 블루투스가 자동으로 활성화됩니다. 블루투스 액티비티를 수행하기 전에 기기 검색 기능을 일관되게 활성화하려면 위의 2단계를 건너뛸 수 있습니다. 아래의 검색 기능 활성화를 읽어보세요.

기기 찾기

BluetoothAdapter를 사용하면 기기 검색을 통해 또는 페어링된(연결된) 기기의 목록을 쿼리하여 원격 블루투스 기기를 찾을 수 있습니다.

기기 검색은 블루투스 사용 기기에 대해 로컬 영역을 검색하고 각각에 대한 정보를 요청하는 검색 절차입니다("조회" 또는 "스캐닝"이라고도 함). 그러나 로컬 영역 내의 블루투스 기기는 검색 가능하도록 현재 활성화된 경우에만 검색 요청에 응답합니다. 기기는 검색 가능한 경우 기기 이름, 클래스 및 고유 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 주소입니다. 이 예시에서 MAC 주소는 사용자에게 표시되는 ArrayAdapter의 일부로 저장됩니다. MAC 주소는 연결을 시작하도록 나중에 추출할 수 있습니다. 연결 생성에 대한 자세한 내용은 기기 연결 섹션을 참조하세요.

기기 검색

기기 검색을 시작하려면 startDiscovery()를 호출합니다. 이는 비동기 프로세스이며 해당 메서드는 검색이 성공적으로 시작했는지 여부를 나타내는 부울을 즉시 반환합니다. 검색 프로세스는 일반적으로 12초 정도의 조회 스캔과, 블루투스 이름을 가져오는 검색된 각 기기의 페이지 스캔을 포함합니다.

애플리케이션은 검색된 각 기기에 대한 정보를 수신하기 위해 ACTION_FOUND 인텐트에 대한 BroadcastReceiver를 등록해야 합니다. 시스템은 각 기기에 대해 ACTION_FOUND 인텐트를 브로드캐스트합니다. 이 인텐트는 BluetoothDeviceBluetoothClass을 각각 포함하는 추가 필드 EXTRA_DEVICEEXTRA_CLASS을 제공합니다. 예를 들어, 기기를 검색할 때 브로드캐스트를 처리하도록 등록할 수 있는 방법은 다음과 같습니다.

// 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 주소입니다. 이 예시에서 MAC 주소는 사용자에게 표시되는 ArrayAdapter의 일부로 저장됩니다. MAC 주소는 연결을 시작하도록 나중에 추출할 수 있습니다. 연결 생성에 대한 자세한 내용은 기기 연결 섹션을 참조하세요.

주의: 기기 검색은 리소스 사용량이 많은 블루투스 어댑터 프로시저입니다. 연결할 기기를 검색한 경우 연결을 시도하기 전에 항상 cancelDiscovery()를 사용하여 검색을 중단해야 합니다. 또한 이미 기기와 연결 중인 경우 검색은 연결에 사용 가능한 대역폭을 상당히 줄일 수 있으므로 연결 중에 검색을 해서는 안 됩니다.

검색 기능 활성화

로컬 기기를 다른 기기가 검색할 수 있게 하려면 ACTION_REQUEST_DISCOVERABLE 작업 인텐트를 사용하여 startActivityForResult(Intent, int)을 호출합니다. 그러면 (애플리케이션을 중지하지 않고) 시스템 설정을 통한 검색 가능 모드 활성화 요청이 발급됩니다. 기본적으로 기기가 120초 동안 검색 가능하게 됩니다. EXTRA_DISCOVERABLE_DURATION 인텐트 엑스트라를 추가하여 다른 기간을 정의할 수 있습니다. 앱이 설정할 수 있는 최대 기간은 3600초이며 값이 0인 경우 기기가 항상 검색 가능합니다. 0 미만 또는 3600 초과 값은 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입니다.

참고: 기기에서 블루투스를 활성화하지 않은 경우 기기 검색 기능을 활성화하면 블루투스가 자동으로 활성화됩니다.

기기는 할당된 시간 동안 검색 가능 모드를 유지합니다. 검색 가능 모드 변경 알림을 받으려면 ACTION_SCAN_MODE_CHANGED 인텐트에 대해 BroadcastReceiver를 등록할 수 있습니다. 이는 새로운 스캔 모드와 이전 스캔 모드를 각각 알려주는 추가 필드 EXTRA_SCAN_MODEEXTRA_PREVIOUS_SCAN_MODE를 포함합니다. 각각에 대해 가능한 값은 SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE 또는 SCAN_MODE_NONE이며, 각 값은 기기가 검색 모드이거나, 검색 모드는 아니지만 연결을 수신할 수 있거나, 검색 모드도 아니고 연결도 수신할 수 없음을 나타냅니다.

원격 기기에 대한 연결을 시작하려면 기기 검색 기능을 활성화할 필요가 없습니다. 원격 기기는 연결을 시작하기 전에 기기를 검색할 수 있어야 하므로 들어오는 연결을 수락하는 서버 소켓을 애플리케이션이 호스팅하려는 경우에만 검색 기능 활성화가 필요합니다.

기기 연결

두 기기에서 애플리케이션 간 연결을 생성하려면 서버측 메커니즘과 클라이언트측 메커니즘을 모두 구현해야 합니다. 왜냐하면 한 기기는 서버 소켓을 열고 다른 기기는 (서버 기기의 MAC 주소를 사용하여) 연결을 시작해야 하기 때문입니다. 서버와 클라이언트는 각각 동일한 RFCOMM 채널에 연결된 BluetoothSocket이 있는 경우 서로 연결된 것으로 간주됩니다. 이 지점에서 각 기기가 입력 및 출력 스트림을 획득할 수 있고 데이터 전송이 시작할 수 있는데, 이에 대해서는 연결 관리 섹션에서 설명합니다. 이 섹션에서는 두 기기 간 연결을 시작하는 방법에 대해 설명합니다.

서버 기기와 클라이언트 기기는 각각 필요한 BluetoothSocket을 서로 다른 방식으로 획득합니다. 서버는 들어오는 연결을 수락할 때 이를 수신합니다. 클라이언트는 서버에 대한 RFCOMM 채널을 열 때 이를 수신합니다.

그림 3: 블루투스 페어링 대화상자.

한 구현 기술은 각 기기가 서버 소켓을 열고 연결을 수신 대기하도록 각 기기를 서버로 자동으로 준비하는 것입니다. 그러면 둘 중 한 기기가 다른 기기와의 연결을 시작하고 클라이언트가 될 수 있습니다. 또는 한 기기는 연결을 명시적으로 "호스팅"하고 요청 시 서버 소켓을 열 수 있고, 다른 기기는 연결을 시작할 수 있습니다.

참고: 이전에 두 기기가 페어링되지 않은 경우 그림 3과 같이 Android 프레임워크가 연결 과정 동안 사용자에게 페어링 요청 알림 또는 대화상자를 자동으로 표시합니다. 따라서 기기 연결을 시도할 때 애플리케이션은 기기 페어링 여부에 대해 걱정할 필요가 없습니다. RFCOMM 연결 시도는 사용자가 성공적으로 페어링할 때까지 차단되거나, 사용자가 페어링을 거부하거나 페어링이 실패하거나 시간이 초과하는 경우 실패합니다.

서버로 연결

두 기기를 연결하려면 한 기기는 열린 BluetoothServerSocket을 제공하여 서버로 작동해야 합니다. 서버 소켓의 목적은 들어오는 연결 요청을 수신 대기하고 수락 시 연결된 BluetoothSocket을 제공하는 것입니다. BluetoothServerSocket에서 BluetoothSocket을 가져올 경우 추가 연결을 수락하지 않으려면 BluetoothServerSocket을 취소할 수 있으며 취소해야 합니다.

다음은 서버 소켓을 설정하고 연결을 수락하는 기본적인 절차입니다.

  1. listenUsingRfcommWithServiceRecord(String, UUID)를 호출하여 BluetoothServerSocket을 가져옵니다.

    이 문자열은 식별 가능한 서비스 이름이며 시스템이 이를 기기의 새 SDP(Service Discovery Protocol) 데이터베이스 항목에 자동으로 기록합니다(이름은 임의로 지정할 수 있으며 단순히 애플리케이션 이름일 수 있음). 또한 UUID는 SDP 항목에 포함되며 클라이언트 기기와의 연결 동의의 기준이 됩니다. 즉, 클라이언트는 이 기기와 연결을 시도할 때 연결할 서비스를 고유하게 식별하는 UUID를 제공합니다. 이 UUID는 (다음 단계에서) 연결이 수락되도록 일치해야 합니다.

  2. accept()를 호출하여 연결 요청에 대한 수신 대기를 시작합니다.

    이는 호출 차단이며, 연결이 수락되거나 예외가 발생한 경우 반환됩니다. 원격 기기가 이 수신 대기 서버 소켓에 등록된 것과 일치하는 UUID를 사용하여 연결 요청을 보내는 경우에만 연결이 수락됩니다. 성공적으로 수행된 경우 accept()는 연결된 BluetoothSocket을 반환합니다.

  3. 추가 연결을 수락하지 않으려면 close()를 호출합니다.

    그러면 서버 소켓과 모든 리소스가 해제되지만 accept()가 반환한 연결된 BluetoothSocket이 닫히지 않습니다. TCP/IP와 달리 RFCOMM은 한 번에 채널당 단일 연결 클라이언트만 허용하므로 대부분의 경우에 연결된 소켓을 수락한 직후에 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) { }
    }
}

이 예에서 들어오는 연결은 하나만 있는 것이 바람직하므로 애플리케이션은 연결이 수락되고 BluetoothSocket을 가져오자마자 가져온 BluetoothSocket을 개별 스레드로 보내고 BluetoothServerSocket을 닫고 해당 루프를 중단합니다.

accept()BluetoothSocket을 반환할 때 해당 소켓은 이미 연결되어 있으므로 (클라이언트 측에서 수행할 때) connect()를 호출해서는 됩니다.

manageConnectedSocket()은 데이터 전송용 스레드를 시작하는 애플리케이션의 가상의 메서드인데, 이에 대해서는 연결 관리 섹션에서 설명합니다.

일반적으로 들어오는 연결에 대한 수신을 완료하자마자 BluetoothServerSocket을 닫아야 합니다. 이 예시에서는BluetoothSocket을 가져오자마자 close()를 호출했습니다. 서버 소켓에서 수신을 중지할 필요가 있는 경우 전용 BluetoothSocket을 닫을 수 있는 공용 메서드를 스레드에서 제공할 수도 있습니다.

클라이언트로 연결

원격 기기(열린 서버 소켓을 제공하는 기기)와의 연결을 시작하려면 먼저 원격 기기를 나타내는 BluetoothDevice 객체를 가져와야 합니다. (BluetoothDevice 가져오기는 위의 기기 찾기 섹션에서 설명했습니다.) 그리고 나서 BluetoothDevice를 사용하여BluetoothSocket을 가져오고 연결을 시작해야 합니다.

기본 절차는 다음과 같습니다.

  1. BluetoothDevice를 통해 createRfcommSocketToServiceRecord(UUID)를 호출하여 BluetoothSocket을 가져옵니다.

    그러면 BluetoothDevice에 연결될 BluetoothSocket이 초기화됩니다. 여기에 전달된 UUID는 (listenUsingRfcommWithServiceRecord(String, UUID))를 사용하여)BluetoothServerSocket을 열 때 서버 기기가 사용한 UUID와 일치해야 합니다. 동일한 UUID를 사용하는 것은 UUID 문자열을 애플리케이션에 하드 코딩하여 서버 및 클라이언트 코드에서 참조하는 것입니다.

  2. connect()를 호출하여 연결을 시작합니다.

    이 호출 시 시스템은 UUID를 매칭하기 위해 원격 기기에서 SDP 조회를 수행합니다. 이 조회가 성공적이고 원격 기기가 연결을 수락하는 경우 원격 기기가 연결 동안 사용할 RFCOMM 채널을 공유하고 connect()가 반환됩니다. 이 메서드는 차단 호출입니다. 어떤 이유로 연결이 실패하거나 connect() 메서드가 (약 12초 후에) 시간이 초과되면 예외가 발생합니다.

    connect()는 차단 호출이므로 이 연결 절차는 항상 메인 액티비티 스레드와는 별개의 스레드에서 수행해야 합니다.

    참고: connect()를 호출할 때 항상 기기가 기기 검색을 수행하지 않도록 해야 합니다. 검색이 진행 중인 경우에는 연결 시도가 현저히 느려지고 실패할 가능성이 높습니다.

다음은 블루투스 연결을 시작하는 스레드의 기본적인 예입니다.

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()를 호출하여 정리해야 합니다. 그러면 즉시 연결된 소켓이 닫히고 모든 내부 리소스가 정리됩니다.

연결 관리

두 대 이상의 기기를 성공적으로 연결한 경우 각 기기는 연결된 BluetoothSocket을 갖습니다. 연결은 기기 간 데이터를 공유할 수 있으므로 즐거움의 출발점입니다. BluetoothSocket을 사용하여 임의의 데이터를 전송하는 일반적인 절차는 다음과 같이 간단합니다.

  1. getInputStream()getOutputStream()을 각각 사용하여 소켓을 통한 전송을 처리하는 InputStreamOutputStream을 가져옵니다.
  2. 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[])가 스트림에서 바이트를 반환하는 경우 해당 데이터는 부모 클래스의 멤버 핸들러를 사용하여 메인 액티비티로 보내집니다. 그런 다음 돌아가서 스트림에서 추가 바이트를 기다립니다.

나가는 데이터를 보내는 것은 메인 액티비티에서 스레드의 write() 메서드를 호출하고 보낼 바이트를 전달하는 것만큼 간단합니다. 이때 이 메서드는 write(byte[])를 호출하여 데이터를 원격 기기에 보냅니다.

스레드의 cancel() 메서드는 BluetoothSocket을 닫아 언제든지 연결을 종료할 수 있도록 하므로 중요합니다. 블루투스 연결 사용을 완료한 경우 항상 이 메서드를 호출해야 합니다.

Bluetooth API 사용 데모는 블루투스 채팅 샘플 앱을 참조하세요.

프로필 작업

Android 3.0부터 Bluetooth API는 Bluetooth 프로필 작업에 대한 지원을 포함합니다. Bluetooth 프로필은 기기 간 블루투스 기반 통신에 대한 무선 인터페이스 사양입니다. 일례로 Hands-Free 프로필이 있습니다. 휴대폰이 무선 헤드셋에 연결하려면 두 기기가 Hands-Free 프로필을 지원해야 합니다.

인터페이스 BluetoothProfile을 구현하여 특정 Bluetooth 프로필을 지원하는 자신만의 클래스를 작성할 수 있습니다. Android Bluetooth API는 다음 Bluetooth 프로필에 대한 구현을 제공합니다.

  • 헤드셋. Headset 프로필은 블루투스 헤드셋이 휴대폰과 함께 사용할 수 있도록 지원합니다. Android는 프로세스 간 통신(IPC)을 통해 블루투스 헤드셋 서비스를 제어하는 프록시인 BluetoothHeadset 클래스를 제공합니다. 여기에는 Bluetooth Headset 및 Hands-Free(v1.5) 프로필이 모두 포함되어 있습니다. BluetoothHeadset 클래스는 AT 명령어에 대한 지원을 포함합니다. 이 주제에 대한 자세한 내용은 공급업체 특정 AT 명령어을 참조하세요.
  • A2DP. A2DP(Advanced Audio Distribution Profile)는 블루투스 연결을 통해 고품질 오디오가 기기 간 스트리밍할 수 있는 방법을 정의합니다. Android는 IPC를 통해 블루투스 A2DP 서비스를 제어하는 프록시인 BluetoothA2dp 클래스를 제공합니다.
  • 의료 기기. Android 4.0(API 레벨 14)은 블루투스 의료 기기 프로필(HDP)에 대한 지원을 소개합니다. 이를 사용하면 블루투스를 사용하여 블루투스를 지원하는 의료 기기(예: 심박측정기, 혈압계, 체온계, 체중계)와 통신하는 애플리케이션을 만들 수 있습니다. 지원되는 기기 목록 및 해당 기기 데이터 특수화 코드 목록은 www.bluetooth.org에 있는 Bluetooth Assigned Numbers를 참조하세요. 이러한 값은 또한 Nomenclature Codes Annex의 MDC_DEV_SPEC_PROFILE_*로서 ISO/IEEE 11073-20601 [7] 사양에서 참조합니다. HDP에 대한 자세한 내용은 의료 기기 프로필을 참조하세요.

다음은 기본적인 프로필 작업 단계입니다.

  1. 블루투스 설정에서 설명한 기본 어댑터를 가져옵니다.
  2. getProfileProxy()를 사용하여 프로필에 연결된 프로필 프록시 객체에 대한 연결을 설정합니다. 아래 예에서 프로필 프록시 객체는 BluetoothHeadset의 인스턴스입니다.
  3. BluetoothProfile.ServiceListener를 설정합니다. 이 리스너는 서비스에 연결되었거나 연결이 끊어진 경우 BluetoothProfile IPC 클라이언트에 알립니다.
  4. onServiceConnected()에서 프로필 프록시 객체에 대한 핸들을 가져옵니다.
  5. 프로필 프록시 객체가 있는 경우 해당 객체를 사용하여 연결 상태를 모니터링하고 해당 프로필과 관련된 다른 작업을 수행할 수 있습니다.

예를 들어, 다음 코드 스니펫은 헤드셋 프로필을 제어할 수 있도록 BluetoothHeadset 프록시 객체에 연결하는 방법을 보여줍니다.

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 명령)의 시스템 브로드캐스트를 수신하도록 등록할 수 있습니다. 예를 들어, 애플리케이션은 연결된 기기의 배터리 레벨을 나타내는 브로드캐스트를 수신할 수 있으며 사용자에게 알리거나 필요한 다른 조치를 취할 수 있습니다. ACTION_VENDOR_SPECIFIC_HEADSET_EVENT 인텐트가 헤드셋에 대한 공급업체 특정 AT 명령어를 처리하는 브로드캐스트 수신기를 생성합니다.

의료 기기 프로필

Android 4.0(API 레벨 14)은 HDP(블루투스 의료 기기 프로필)에 대한 지원을 소개합니다. 이를 사용하면 블루투스를 사용하여 블루투스를 지원하는 의료 기기(예: 심박측정기, 혈압계, 체온계, 체중계)와 통신하는 애플리케이션을 만들 수 있습니다. Bluetooth Health API는 BluetoothHealth, BluetoothHealthCallbackBluetoothHealthAppConfiguration 클래스를 포함하는데, 이에 대해서는 기본 사항에서 설명했습니다.

Bluetooth Health API를 사용할 때 다음과 같은 핵심 HDP 개념을 이해하면 유용합니다.

개념 설명
소스 HDP에 정의된 역할입니다. 소스는 의료 데이터를 스마트 기기(예: Android 폰 또는 태블릿)에 전송하는 의료 기기(예: 체중계, 혈당측정기, 체온계)입니다.
싱크 HDP에 정의된 역할입니다. HDP에서 싱크는 의료 데이터를 수신하는 스마트 기기입니다. Android HDP 애플리케이션에서 싱크는 BluetoothHealthAppConfiguration 객체가 나타냅니다.
등록 특정 의료 기기에 대한 싱크 등록을 말합니다.
연결 의료 기기와 스마트 기기(예: Android 폰 또는 태블릿) 사이의 채널 개방을 말합니다.

HDP 애플리케이션 만들기

다음은 Android HDP 애플리케이션을 만드는 기본 단계입니다.

  1. BluetoothHealth 프록시 객체에 대한 참조를 가져옵니다.

    일반 헤드셋 및 A2DP 프로필 기기와 마찬가지로 프로필 프록시 객체와 연결을 설정하도록 BluetoothProfile.ServiceListenerHEALTH 프로필 유형과 함께 getProfileProxy()를 호출해야 합니다.

  2. BluetoothHealthCallback을 만들고 의료 싱크로 작동하는 애플리케이션 구성(BluetoothHealthAppConfiguration)을 등록합니다.
  3. 의료 기기 연결을 설정합니다. 일부 기기가 연결을 시작합니다. 해당 기기에 대해서는 이 단계를 수행할 필요가 없습니다.
  4. 의료 기기에 성공적으로 연결되었을 때 파일 설명자를 사용하여 의료 기기에 읽고 씁니다.

    수신된 데이터는 IEEE 11073-xxxxx 사양을 구현하는 의료 관리자를 사용하여 해석해야 합니다.

  5. 완료되면 의료 채널을 닫고 애플리케이션 등록을 취소합니다. 확장된 비활성이 있는 경우에도 채널이 닫힙니다.

이 단계를 보여주는 전체 코드 샘플은 블루투스 HDP(의료 기기 프로필)을 참조하세요.