Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

USB 호스트 개요

Android 지원 기기가 USB 호스트 모드일 때, 이 기기는 USB 호스트 역할을 하며 버스에 전원을 공급하고 연결된 USB 기기를 열거합니다. USB 호스트 모드는 Android 3.1 이상에서 지원됩니다.

API 개요

시작하기 전에 먼저 사용해야 하는 클래스를 이해하는 것이 중요합니다. 다음 표에서는 android.hardware.usb 패키지의 USB 호스트 API를 설명합니다.

표 1. USB 호스트 API

클래스 설명
UsbManager 이 클래스를 사용하면 연결된 USB 기기를 열거하고 기기와 통신할 수 있습니다.
UsbDevice 이 클래스는 연결된 USB 기기를 나타내며 식별 정보, 인터페이스 및 엔드포인트에 액세스하는 메서드를 포함하고 있습니다.
UsbInterface 기기의 기능 세트를 정의하는 USB 기기 인터페이스를 나타냅니다. 기기에는 통신할 인터페이스가 하나 이상 있을 수 있습니다.
UsbEndpoint 인터페이스 엔드포인트를 나타내며 이 인터페이스의 통신 채널입니다. 인터페이스에는 엔드포인트가 하나 이상 있을 수 있으며 일반적으로 기기와의 양방향 통신을 위한 입력 및 출력 엔드포인트가 있습니다.
UsbDeviceConnection 엔드포인트에서 데이터를 전송하는 기기와의 연결을 나타냅니다. 이 클래스를 통해 데이터를 동기적 또는 비동기적으로 주고받을 수 있습니다.
UsbRequest UsbDeviceConnection을 통해 기기와 통신하는 비동기식 요청을 나타냅니다.
UsbConstants Linux 커널의 linux/usb/ch9.h에 있는 정의에 상응하는 USB 상수를 정의합니다.

대부분의 상황에서 USB 기기와 통신할 때 이러한 클래스를 모두 사용해야 합니다(UsbRequest는 비동기 통신을 할 때만 필요함). 일반적으로 원하는 UsbDevice를 검색하려면 UsbManager를 얻어야 합니다. 보유하는 기기에서 통신하려면 적절한 UsbInterface 및 인터페이스의 UsbEndpoint를 찾아야 합니다. 적절한 엔드포인트를 얻었으면 USB 기기와 통신할 UsbDeviceConnection을 열어야 합니다.

Android manifest 요구사항

다음 목록에서는 USB 호스트 API로 작업하기 전에 애플리케이션의 manifest 파일에 추가해야 하는 항목을 설명합니다.

  • 모든 Android 지원 기기가 USB 호스트 API를 지원한다는 보장이 없으므로 애플리케이션에서 android.hardware.usb.host 기능을 사용한다는 것을 선언하는 <uses-feature> 요소를 포함해야 합니다.
  • 애플리케이션의 최소 SDK를 API 레벨 12 이상으로 설정해야 합니다. 이전 API 레벨에서는 USB 호스트 API가 제공되지 않습니다.
  • 애플리케이션에서 연결된 USB 기기에 관한 알림을 받도록 하려면 기본 활동에서 android.hardware.usb.action.USB_DEVICE_ATTACHED 인텐트의 <intent-filter><meta-data> 요소 쌍을 지정합니다. <meta-data> 요소는 감지하려는 기기에 관한 식별 정보를 선언하는 외부 XML 리소스 파일을 가리킵니다.

    XML 리소스 파일에서 필터링하려는 USB 기기의 <usb-device> 요소를 선언해야 합니다. 다음 목록은 <usb-device>의 속성을 설명합니다. 일반적으로 특정 기기를 필터링하려면 공급업체 및 제품 ID를 사용하고 대용량 저장 기기 또는 디지털카메라와 같은 USB 기기 그룹을 필터링하려면 클래스, 서브클래스 및 프로토콜을 사용하세요. 이러한 속성을 모두 지정하거나 전혀 지정하지 않을 수 있습니다. 속성을 지정하지 않으면 모든 USB 기기를 검색하므로 애플리케이션에서 모든 기기가 필요할 때만 속성을 지정하지 않아야 합니다.

    • vendor-id
    • product-id
    • class
    • subclass
    • protocol(기기 또는 인터페이스)

    리소스 파일을 res/xml/ 디렉터리에 저장해야 합니다. 리소스 파일 이름(.xml 확장자 제외)은 <meta-data> 요소에서 지정한 이름과 동일해야 합니다. XML 리소스 파일의 형식은 아래 에 나와 있습니다.

manifest 및 리소스 파일 예

다음 예는 샘플 manifest 및 이에 상응하는 리소스 파일을 보여줍니다.

    <manifest ...>
        <uses-feature android:name="android.hardware.usb.host" />
        <uses-sdk android:minSdkVersion="12" />
        ...
        <application>
            <activity ...>
                ...
                <intent-filter>
                    <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
                </intent-filter>

                <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                    android:resource="@xml/device_filter" />
            </activity>
        </application>
    </manifest>
    

여기서는 다음 리소스 파일을 res/xml/device_filter.xml에 저장해야 합니다. 이 파일에서는 명시된 속성이 있는 USB 기기를 필터링하도록 지정합니다.

    <?xml version="1.0" encoding="utf-8"?>

    <resources>
        <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
    </resources>
    

기기 사용

사용자가 USB 기기를 Android 지원 기기에 연결할 때 Android 시스템은 애플리케이션이 연결된 기기에 관심을 보이는지 확인할 수 있습니다. 관심을 보이는 경우 필요하다면 기기와의 통신을 설정할 수 있습니다. 통신을 설정하려면 애플리케이션에서 다음과 같이 해야 합니다.

  1. 사용자가 USB 기기를 연결할 때 알림을 받도록 인텐트 필터를 사용하거나 이미 연결된 USB 기기를 열거하여 연결된 USB 기기를 찾습니다.
  2. USB 기기에 연결할 수 있는 권한이 아직 없다면 사용자에게 권한을 요청합니다.
  3. 적절한 인터페이스 엔드포인트에서 데이터를 읽고 쓰는 방법으로 USB 기기와 통신합니다.

기기 찾기

애플리케이션은 사용자가 USB 기기를 연결할 때 알림을 받도록 인텐트 필터를 사용하거나 이미 연결된 USB 기기를 열거하여 USB 기기를 찾을 수 있습니다. 애플리케이션에서 원하는 기기를 자동으로 감지할 수 있게 하려면 인텐트 필터를 사용하는 방법이 유용합니다. 연결된 모든 기기 목록을 확보하려면 또는 애플리케이션이 인텐트를 필터링하지 않는다면 연결된 USB 기기를 열거하는 방법이 유용합니다.

인텐트 필터 사용

애플리케이션에서 특정 USB 기기를 찾도록 하려면 android.hardware.usb.action.USB_DEVICE_ATTACHED 인텐트를 필터링하는 인텐트 필터를 지정하면 됩니다. 이 인텐트 필터와 함께 제품 및 공급업체 ID와 같은 USB 기기의 속성을 명시하는 리소스 파일을 지정해야 합니다. 사용자가 기기 필터와 일치하는 기기를 연결할 때 시스템에서 사용자에게 애플리케이션을 시작할지 묻는 대화상자를 표시합니다. 사용자가 수락하면 애플리케이션은 기기 연결이 끊어질 때까지 기기에 액세스할 수 있는 권한을 자동으로 갖게 됩니다.

다음 예는 인텐트 필터를 선언하는 방법을 보여줍니다.

    <activity ...>
    ...
        <intent-filter>
            <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
        </intent-filter>

        <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
            android:resource="@xml/device_filter" />
    </activity>
    

다음 예는 상응하는 리소스 파일을 선언하는 방법을 보여줍니다. 이 파일은 관심 있는 USB 기기를 지정합니다.

    <?xml version="1.0" encoding="utf-8"?>

    <resources>
        <usb-device vendor-id="1234" product-id="5678" />
    </resources>
    

다음과 같이 활동의 인텐트에서 연결된 기기를 나타내는 UsbDevice를 얻을 수 있습니다.

Kotlin

    val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
    

자바

    UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
    

기기 열거

애플리케이션이 실행되는 동안 현재 연결된 모든 USB 기기를 검사하는 데 관심이 있다면 버스의 기기를 열거할 수 있습니다. getDeviceList() 메서드를 사용하면 연결된 모든 USB 기기의 해시 맵을 가져올 수 있습니다. 맵에서 기기를 얻으려면 해시 맵에 USB 기기 이름으로 키를 지정합니다.

Kotlin

    val manager = getSystemService(Context.USB_SERVICE) as UsbManager
    ...
    val deviceList = manager.getDeviceList()
    val device = deviceList.get("deviceName")
    

자바

    UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
    ...
    HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
    UsbDevice device = deviceList.get("deviceName");
    

원한다면 다음과 같이 해시 맵에서 반복자를 가져와 각 기기를 하나씩 처리할 수도 있습니다.

Kotlin

    val manager = getSystemService(Context.USB_SERVICE) as UsbManager
    ..
    val deviceList: HashMap<String, UsbDevice> = manager.deviceList
    deviceList.values.forEach { device ->
        //your code
    }
    

자바

    UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
    ...
    HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
    Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
    while(deviceIterator.hasNext()){
        UsbDevice device = deviceIterator.next();
        //your code
    }
    

기기와 통신할 수 있는 권한 얻기

USB 기기와 통신하려면 먼저 애플리케이션에서 사용자로부터 권한을 받아야 합니다.

참고: 애플리케이션이 인텐트 필터를 사용하여 연결된 USB 기기를 검색할 때 사용자가 애플리케이션에서 인텐트를 처리하도록 허용하면 자동으로 권한을 받습니다. 권한을 자동으로 받지 못하면 기기에 연결하기 전에 애플리케이션에서 명시적으로 권한을 요청해야 합니다.

애플리케이션이 이미 연결된 USB 기기를 열거한 후 기기와 통신하려는 때와 같은 일부 상황에서는 명시적으로 권한을 요청해야 할 수 있습니다. 기기와 통신하려고 하기 전에 기기에 액세스할 수 있는 권한이 있는지 확인해야 합니다. 권한을 확인하지 않고 통신을 시도했는데 사용자가 기기 액세스 권한을 거부하면 런타임 오류가 발생합니다.

명시적으로 권한을 얻으려면 먼저 broadcast receiver를 생성하세요. 수신자는 requestPermission()이 호출될 때 브로드캐스트를 받는 인텐트를 수신 대기합니다. requestPermission()이 호출되면 사용자에게 기기 연결 권한을 요청하는 대화상자가 표시됩니다. 다음 샘플 코드는 broadcast receiver를 생성하는 방법을 보여줍니다.

Kotlin

    private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"

    private val usbReceiver = object : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            if (ACTION_USB_PERMISSION == intent.action) {
                synchronized(this) {
                    val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)

                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        device?.apply {
                            //call method to set up device communication
                        }
                    } else {
                        Log.d(TAG, "permission denied for device $device")
                    }
                }
            }
        }
    }
    

자바

    private static final String ACTION_USB_PERMISSION =
        "com.android.example.USB_PERMISSION";
    private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {

        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) {
                synchronized (this) {
                    UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        if(device != null){
                          //call method to set up device communication
                       }
                    }
                    else {
                        Log.d(TAG, "permission denied for device " + device);
                    }
                }
            }
        }
    };
    

broadcast receiver를 등록하려면 활동의 onCreate() 메서드에 다음 코드를 추가하세요.

Kotlin

    private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"
    ...
    val manager = getSystemService(Context.USB_SERVICE) as UsbManager
    ...
    permissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), 0)
    val filter = IntentFilter(ACTION_USB_PERMISSION)
    registerReceiver(usbReceiver, filter)
    

자바

    UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
    private static final String ACTION_USB_PERMISSION =
        "com.android.example.USB_PERMISSION";
    ...
    permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
    IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
    registerReceiver(usbReceiver, filter);
    

사용자에게 기기 연결 권한을 요청하는 대화상자를 표시하려면 다음과 같이 requestPermission() 메서드를 호출하세요.

Kotlin

    lateinit var device: UsbDevice
    ...
    usbManager.requestPermission(device, permissionIntent)
    

자바

    UsbDevice device;
    ...
    usbManager.requestPermission(device, permissionIntent);
    

사용자가 대화상자에 응답할 때 broadcast receiver는 EXTRA_PERMISSION_GRANTED 엑스트라가 포함된 인텐트를 수신하는데 이 엑스트라는 응답을 나타내는 부울입니다. 기기에 연결하기 전에 이 엑스트라가 true 값인지 확인해야 합니다.

기기와 통신

USB 기기와의 통신은 동기적 또는 비동기적일 수 있습니다. 동기적이든, 비동기적이든 모든 데이터 전송을 실행할 새 스레드를 생성해야 하므로 UI 스레드를 차단하지 않아야 합니다. 기기와의 통신을 적절하게 설정하려면 통신하려는 기기의 적절한 UsbInterfaceUsbEndpoint를 얻어 UsbDeviceConnection을 통해 엔드포인트에서 요청을 보내야 합니다. 일반적으로 코드에서 다음과 같이 해야 합니다.

  • 제품 ID, 공급업체 ID 또는 기기 클래스와 같은 UsbDevice 객체 속성을 확인하여 기기와 통신할지 여부를 파악해야 합니다.
  • 기기와 통신하려는 것이 확실하면 통신하는 데 사용할 적절한 UsbInterface와 함께 이 인터페이스의 적절한 UsbEndpoint를 찾아야 합니다. 인터페이스에는 엔드포인트가 하나 이상 있을 수 있으며 일반적으로 양방향 통신을 위한 입력 및 출력 엔드포인트가 있습니다.
  • 적절한 엔드포인트를 찾으면 이 엔드포인트의 UsbDeviceConnection을 열어야 합니다.
  • bulkTransfer() 또는 controlTransfer() 메서드를 사용하여 엔드포인트에서 전송하려는 데이터를 제공해야 합니다. 기본 UI 스레드가 차단되지 않도록 하려면 다른 스레드에서 이 단계를 실행해야 합니다. Android에서 스레드 사용에 관한 자세한 내용은 프로세스 및 스레드를 참조하세요.

다음 코드 스니펫은 동기식 데이터 전송을 실행하는 간단한 방법입니다. 코드에는 통신할 적절한 인터페이스 및 엔드포인트를 정확하게 찾을 수 있는 더 많은 로직이 있어야 하며 기본 UI 스레드가 아닌 다른 스레드에서 데이터를 전송해야 합니다.

Kotlin

    private lateinit var bytes: ByteArray
    private val TIMEOUT = 0
    private val forceClaim = true

    ...

    device?.getInterface(0)?.also { intf ->
        intf.getEndpoint(0)?.also { endpoint ->
            usbManager.openDevice(device)?.apply {
                claimInterface(intf, forceClaim)
                bulkTransfer(endpoint, bytes, bytes.size, TIMEOUT) //do in another thread
            }
        }
    }
    

자바

    private Byte[] bytes;
    private static int TIMEOUT = 0;
    private boolean forceClaim = true;

    ...

    UsbInterface intf = device.getInterface(0);
    UsbEndpoint endpoint = intf.getEndpoint(0);
    UsbDeviceConnection connection = usbManager.openDevice(device);
    connection.claimInterface(intf, forceClaim);
    connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
    

비동기식으로 데이터를 보내려면 UsbRequest 클래스를 사용하여 비동기식 요청에서 initializequeue 작업을 실행한 후 requestWait()를 통해 결과를 기다리세요.

자세한 내용은 비동기 일괄 전송을 실행하는 방법을 보여주는 AdbTest 샘플 및 인터럽트 엔드포인트를 비동기적으로 수신 대기하는 방법을 보여주는 MissileLauncher 샘플을 참조하세요.

기기와의 통신 종료

기기와의 통신이 완료되었거나 기기가 분리되었다면 releaseInterface()close()를 호출하여 UsbInterfaceUsbDeviceConnection을 닫습니다. 분리 이벤트를 수신 대기하려면 다음과 같이 broadcast receiver를 생성하세요.

Kotlin

    var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {

            if (UsbManager.ACTION_USB_DEVICE_DETACHED == intent.action) {
                val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
                device?.apply {
                    // call your method that cleans up and closes communication with the device
                }
            }
        }
    }
    

자바

    BroadcastReceiver usbReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

          if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                if (device != null) {
                    // call your method that cleans up and closes communication with the device
                }
            }
        }
    };
    

manifest가 아닌 애플리케이션 내에 broadcast receiver를 생성하면 애플리케이션이 실행되는 동안 이 애플리케이션만 분리 이벤트를 처리할 수 있습니다. 이렇게 하면 분리 이벤트가 현재 실행 중인 애플리케이션으로만 전달되고 모든 애플리케이션으로 브로드캐스트되지는 않습니다.