USB-Host – Übersicht

Wenn sich Ihr Android-Gerät im USB-Hostmodus befindet, fungiert es als USB-Host, versorgt den Bus und listet die verbundenen USB-Geräte auf. Der USB-Hostmodus wird ab Android 3.1 unterstützt.

API-Übersicht

Bevor Sie beginnen, sollten Sie wissen, mit welchen Kursen Sie arbeiten müssen. In der folgenden Tabelle werden die USB-Host-APIs im Paket android.hardware.usb beschrieben.

Tabelle 1 USB-Host-APIs

Klasse Beschreibung
UsbManager Ermöglicht Ihnen, angeschlossene USB-Geräte aufzuzählen und mit ihnen zu kommunizieren.
UsbDevice Stellt ein verbundenes USB-Gerät dar und enthält Methoden für den Zugriff auf identifizierende Informationen, Schnittstellen und Endpunkte.
UsbInterface Stellt eine Schnittstelle eines USB-Geräts dar, die eine Reihe von Funktionen für das Gerät definiert. Ein Gerät kann über eine oder mehrere Schnittstellen verfügen, über die es kommunizieren kann.
UsbEndpoint Stellt einen Schnittstellenendpunkt dar, der ein Kommunikationskanal für diese Schnittstelle ist. Eine Schnittstelle kann einen oder mehrere Endpunkte haben und hat in der Regel Ein- und Ausgabeendpunkte für die bidirektionale Kommunikation mit dem Gerät.
UsbDeviceConnection Stellt eine Verbindung zum Gerät dar, das Daten auf Endpunkten überträgt. Mit dieser Klasse können Sie Daten synchron oder asynchron hin- und hersenden.
UsbRequest Stellt eine asynchrone Anfrage zur Kommunikation mit einem Gerät über eine UsbDeviceConnection dar.
UsbConstants Definiert USB-Konstanten, die den Definitionen unter linux/usb/ch9.h des Linux-Kernels entsprechen.

In den meisten Situationen müssen Sie bei der Kommunikation mit einem USB-Gerät alle diese Klassen verwenden (UsbRequest ist nur bei asynchroner Kommunikation erforderlich). Im Allgemeinen rufen Sie einen UsbManager ab, um die gewünschten UsbDevice abzurufen. Wenn Sie das Gerät haben, müssen Sie den entsprechenden UsbInterface und den UsbEndpoint dieser Schnittstelle für die Kommunikation finden. Sobald Sie den richtigen Endpunkt ermittelt haben, öffnen Sie UsbDeviceConnection, um mit dem USB-Gerät zu kommunizieren.

Anforderungen an das Android-Manifest

In der folgenden Liste wird beschrieben, was du der Manifestdatei deiner App hinzufügen musst, bevor du mit den USB Host APIs arbeitest:

  • Da nicht unbedingt alle Android-Geräte die USB-Host-APIs unterstützen, füge ein <uses-feature>-Element ein, das deklariert, dass deine App die Funktion android.hardware.usb.host verwendet.
  • Legen Sie das Mindest-SDK der Anwendung auf API-Level 12 oder höher fest. Die USB-Host-APIs sind auf früheren API-Levels nicht vorhanden.
  • Wenn du möchtest, dass deine Anwendung über ein angeschlossenes USB-Gerät benachrichtigt wird, gib ein <intent-filter>- und ein <meta-data>-Elementpaar für den Intent android.hardware.usb.action.USB_DEVICE_ATTACHED in deiner Hauptaktivität an. Das Element <meta-data> verweist auf eine externe XML-Ressourcendatei, in der personenidentifizierbare Informationen zum Gerät angegeben sind, das erkannt werden soll.

    Deklarieren Sie in der XML-Ressourcendatei <usb-device>-Elemente für die USB-Geräte, die Sie filtern möchten. In der folgenden Liste werden die Attribute von <usb-device> beschrieben. Im Allgemeinen sollten Sie Anbieter- und Produkt-ID verwenden, wenn Sie nach einem bestimmten Gerät filtern möchten. Verwenden Sie Klasse, Unterklasse und Protokoll, wenn Sie nach einer Gruppe von USB-Geräten wie Massenspeichergeräten oder Digitalkameras filtern möchten. Sie können keines oder alle dieser Attribute angeben. Wenn Sie keine Attribute angeben, entspricht dies jedem USB-Gerät. Daher sollten Sie dies nur tun, wenn dies für Ihre Anwendung erforderlich ist:

    • vendor-id
    • product-id
    • class
    • subclass
    • protocol (Gerät oder Schnittstelle)

    Speichern Sie die Ressourcendatei im Verzeichnis res/xml/. Der Name der Ressourcendatei (ohne die Erweiterung „.xml“) muss mit dem übereinstimmen, den Sie im Element <meta-data> angegeben haben. Das Format für die XML-Ressourcendatei ist im Beispiel unten dargestellt.

Beispiele für Manifest- und Ressourcendateien

Das folgende Beispiel zeigt ein Beispielmanifest und die entsprechende Ressourcendatei:

<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>

In diesem Fall sollte die folgende Ressourcendatei in res/xml/device_filter.xml gespeichert werden. Sie gibt an, dass alle USB-Geräte mit den angegebenen Attributen gefiltert werden sollen:

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

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

Mit Geräten arbeiten

Wenn Nutzer USB-Geräte mit einem Android-Gerät verbinden, kann das Android-System ermitteln, ob Ihre App an dem verbundenen Gerät interessiert ist. In diesem Fall können Sie bei Bedarf die Kommunikation mit dem Gerät einrichten. Dazu muss Ihre Anwendung folgende Voraussetzungen erfüllen:

  1. Zum Erkennen verbundener USB-Geräte können Sie einen Intent-Filter verwenden, um benachrichtigt zu werden, wenn der Nutzer ein USB-Gerät anschließt, oder indem Sie bereits verbundene USB-Geräte auflisten.
  2. Bitte den Nutzer um die Berechtigung zum Verbinden mit dem USB-Gerät, falls dies nicht bereits geschehen ist.
  3. Kommunizieren Sie mit dem USB-Gerät, indem Sie Daten auf den entsprechenden Schnittstellenendpunkten lesen und schreiben.

Gerät entdecken

Deine Anwendung kann USB-Geräte entweder mithilfe eines Intent-Filters erkennen, um benachrichtigt zu werden, wenn der Nutzer ein Gerät anschließt, oder indem die bereits verbundenen USB-Geräte aufgelistet werden. Die Verwendung eines Intent-Filters ist nützlich, wenn Ihre Anwendung das gewünschte Gerät automatisch erkennen soll. Die Aufzählung verbundener USB-Geräte ist nützlich, wenn Sie eine Liste aller verbundenen Geräte abrufen möchten oder wenn Ihre Anwendung nicht nach einem Intent gefiltert hat.

Intent-Filter verwenden

Damit Ihre Anwendung ein bestimmtes USB-Gerät erkennt, können Sie einen Intent-Filter angeben, um nach dem Intent android.hardware.usb.action.USB_DEVICE_ATTACHED zu filtern. Zusammen mit diesem Intent-Filter müssen Sie eine Ressourcendatei angeben, in der Eigenschaften des USB-Geräts angegeben werden, z. B. das Produkt und die Anbieter-ID. Wenn Nutzer ein Gerät verbinden, das Ihrem Gerätefilter entspricht, zeigt das System ein Dialogfeld an, in dem sie gefragt werden, ob sie Ihre App starten möchten. Wenn die Nutzer zustimmen, hat Ihre Anwendung automatisch die Berechtigung, auf das Gerät zuzugreifen, bis es getrennt wird.

Das folgende Beispiel zeigt, wie der Intent-Filter deklariert wird:

<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>

Das folgende Beispiel zeigt, wie du die entsprechende Ressourcendatei deklarieren kannst, in der die gewünschten USB-Geräte angegeben sind:

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

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

In Ihrer Aktivität können Sie das UsbDevice, das das angeschlossene Gerät darstellt, vom Intent so abrufen:

Kotlin

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

Java

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

Geräte auflisten

Wenn Ihre Anwendung alle USB-Geräte untersuchen möchte, die derzeit verbunden sind, während die Anwendung ausgeführt wird, kann sie die Geräte im Bus auflisten. Verwenden Sie die Methode getDeviceList(), um eine Hashtabelle aller verbundenen USB-Geräte zu erhalten. Die Hash-Zuordnung wird vom Namen des USB-Geräts bestimmt, wenn Sie ein Gerät aus der Karte abrufen möchten.

Kotlin

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

Java

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

Bei Bedarf können Sie auch einfach einen Iterator aus der Hashtabelle abrufen und jedes Gerät einzeln verarbeiten:

Kotlin

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

Java

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
}

Berechtigung zur Kommunikation mit einem Gerät einholen

Für die Kommunikation mit dem USB-Gerät muss die Anwendung über eine entsprechende Berechtigung der Nutzer verfügen.

Hinweis:Wenn Ihre Anwendung einen Intent-Filter verwendet, um USB-Geräte zu erkennen, sobald sie verbunden sind, erhält sie automatisch die Berechtigung, wenn der Nutzer zulässt, dass Ihre Anwendung den Intent verarbeitet. Falls nicht, müssen Sie die Berechtigung ausdrücklich in Ihrer Anwendung anfordern, bevor Sie eine Verbindung zum Gerät herstellen.

Die explizite Berechtigungsanfrage kann in einigen Situationen erforderlich sein, z. B. wenn Ihre Anwendung USB-Geräte aufzählt, die bereits verbunden sind, und dann mit einem Gerät kommunizieren möchten. Sie müssen die Berechtigung für den Zugriff auf ein Gerät überprüfen, bevor Sie versuchen, mit ihm zu kommunizieren. Andernfalls erhalten Sie einen Laufzeitfehler, wenn der Nutzer die Berechtigung für den Zugriff auf das Gerät verweigert hat.

Wenn Sie die Berechtigung explizit einholen möchten, erstellen Sie zuerst einen Übertragungsempfänger. Dieser Empfänger wartet auf den Intent, der gesendet wird, wenn Sie requestPermission() aufrufen. Durch den Aufruf von requestPermission() wird ein Dialogfeld angezeigt, in dem der Nutzer um die Berechtigung zum Herstellen einer Verbindung mit dem Gerät gebeten wird. Der folgende Beispielcode zeigt, wie der Broadcast-Empfänger erstellt wird:

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

Java

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

Fügen Sie dies in die Methode onCreate() in Ihrer Aktivität ein, um den Übertragungsempfänger zu registrieren:

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)

Java

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);

Wenn das Dialogfeld angezeigt werden soll, in dem der Nutzer um die Berechtigung zum Herstellen einer Verbindung mit dem Gerät gebeten wird, rufen Sie die Methode requestPermission() auf:

Kotlin

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

Java

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

Wenn Nutzer auf das Dialogfeld antworten, erhält der Empfänger des Rundfunks den Intent, der das zusätzliche EXTRA_PERMISSION_GRANTED-Element enthält. Dabei handelt es sich um einen booleschen Wert, der die Antwort darstellt. Prüfen Sie dieses Extra auf den Wert „true“, bevor Sie eine Verbindung zum Gerät herstellen.

Mit einem Gerät kommunizieren

Die Kommunikation mit einem USB-Gerät kann entweder synchron oder asynchron erfolgen. In beiden Fällen sollten Sie einen neuen Thread erstellen, für den alle Datenübertragungen ausgeführt werden, damit Sie den UI-Thread nicht blockieren. Damit die Kommunikation mit einem Gerät ordnungsgemäß eingerichtet werden kann, musst du die entsprechenden UsbInterface und UsbEndpoint des Geräts abrufen, auf dem du kommunizieren möchtest, und Anfragen an diesen Endpunkt mit einer UsbDeviceConnection senden. Im Allgemeinen sollte Ihr Code:

  • Prüfe die Attribute eines UsbDevice-Objekts, z. B. Produkt-ID, Anbieter-ID oder Geräteklasse, um herauszufinden, ob du mit dem Gerät kommunizieren möchtest oder nicht.
  • Wenn Sie sicher sind, dass Sie mit dem Gerät kommunizieren möchten, suchen Sie die entsprechende UsbInterface, die Sie für die Kommunikation verwenden möchten, und die entsprechende UsbEndpoint dieser Schnittstelle. Schnittstellen können einen oder mehrere Endpunkte haben und haben in der Regel einen Eingabe- und Ausgabeendpunkt für die bidirektionale Kommunikation.
  • Wenn Sie den richtigen Endpunkt gefunden haben, öffnen Sie UsbDeviceConnection auf diesem Endpunkt.
  • Geben Sie mit der Methode bulkTransfer() oder controlTransfer() die Daten an, die am Endpunkt übertragen werden sollen. Sie sollten diesen Schritt in einem anderen Thread ausführen, um zu verhindern, dass der UI-Hauptthread blockiert wird. Weitere Informationen zur Verwendung von Threads in Android findest du unter Prozesse und Threads.

Das folgende Code-Snippet stellt eine einfache Möglichkeit für die synchrone Datenübertragung dar. Ihr Code sollte mehr Logik enthalten, um die richtige Schnittstelle und Endpunkte für die Kommunikation zu finden, und außerdem Daten in einem anderen Thread als dem Haupt-UI-Thread übertragen:

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

Java

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

Wenn Sie Daten asynchron senden möchten, verwenden Sie die Klasse UsbRequest an initialize und eine asynchrone Anfrage queue. Warten Sie dann auf das Ergebnis mit requestWait().

Kommunikation mit einem Gerät beenden

Wenn die Kommunikation mit einem Gerät abgeschlossen ist oder das Gerät getrennt wurde, schließen Sie UsbInterface und UsbDeviceConnection, indem Sie releaseInterface() und close() aufrufen. Erstellen Sie einen Übertragungsempfänger wie unten, um auf getrennte Ereignisse zu warten:

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

Java

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
            }
        }
    }
};

Wenn Sie den Broadcast-Empfänger innerhalb der Anwendung und nicht im Manifest erstellen, kann Ihre Anwendung nur getrennte Ereignisse verarbeiten, während sie ausgeführt wird. Getrennte Ereignisse werden dann nur an die Anwendung gesendet, die gerade ausgeführt wird, und nicht an alle Anwendungen.