USB 主机概览

当您的 Android 设备处于 USB 主机模式时,它会充当 USB 主机,为总线供电, 并枚举连接的 USB 设备。Android 3.1 及更高版本支持 USB 主机模式。

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 常量 内核版本。

在大多数情况下,您都需要使用所有这些类(仅在进行异步通信时才需要 UsbRequest) 在与 USB 设备通信时会用到。通常,您需要获取 UsbManager 来检索所需的 UsbDevice。 有了设备后,您需要查找相应的 UsbInterface 及其 UsbEndpoint 进行通信获得正确的端点后,打开 UsbDeviceConnection 以与 USB 设备通信。

Android 清单要求

以下列表介绍了您需要先向应用的清单文件中添加的内容,然后才能 使用 USB 主机 API:

  • 由于并非所有 Android 设备都保证支持 USB 主机 API, 添加一个 <uses-feature> 元素,用于声明您的应用使用 android.hardware.usb.host 功能。
  • 将应用的最低 SDK 设置为 API 级别 12 或更高级别。USB 主机 API 不支持 存在的一些限制
  • 如果您希望自己的应用在连接 USB 设备时收到通知,请指定 <intent-filter><meta-data> 元素对 android.hardware.usb.action.USB_DEVICE_ATTACHED intent。通过 <meta-data> 元素指向声明 待检测设备的识别信息。

    在 XML 资源文件中,为 USB 声明 <usb-device> 元素 您要过滤的设备以下列表介绍了 <usb-device>。一般来说,如果您要过滤广告,请使用供应商 ID 和产品 ID 如果您想过滤出某个组,请使用类、子类和协议 大容量存储设备或数码相机等 USB 设备。您可以指定不指定任何一项 所有这些属性如果不指定任何属性,则会匹配每个 USB 设备,因此请仅这样做 如果您的应用需要它:

    • vendor-id
    • product-id
    • class
    • subclass
    • protocol(设备或接口)

    将资源文件保存在 res/xml/ 目录中。资源文件名 (没有 .xml 扩展名)必须与您在 <meta-data> 元素。XML 资源文件格式为 示例

清单和资源文件示例

以下示例展示了一个清单及其相应的资源文件:

<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 并指定具有指定 属性:

<?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. 使用 intent 过滤器发现连接的 USB 设备,以便在用户时收到通知 连接 USB 设备,或枚举已连接的 USB 设备。
  2. 请求用户授予连接到 USB 设备的权限(如果尚未获得权限)。
  3. 通过在相应接口上读取和写入数据,与 USB 设备通信 端点。

发现设备

您的应用可以使用 Intent 过滤器发现 USB 设备,方法是: 用户连接设备,或枚举已连接的 USB 设备。使用 如果您希望应用自动检测 所需的设备如果您想获取所有 USB 设备的列表, 连接的设备,或者您的应用未针对某个 intent 进行过滤。

使用 Intent 过滤器

要让您的应用发现特定的 USB 设备,可以指定一个 intent 过滤器, android.hardware.usb.action.USB_DEVICE_ATTACHED intent 的过滤器。以及 此 intent 过滤器,您需要指定一个资源文件来指定 USB 接口的属性 例如产品和供应商 ID。当用户连接与您的设备匹配的设备时 过滤器,系统将向其显示一个对话框,询问他们是否要启动您的应用。 如果用户接受,您的应用会自动获得访问设备的权限,直到 设备已断开连接。

以下示例展示了如何声明 Intent 过滤器:

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

在您的 activity 中,您可以获取代表UsbDevice 从 intent 中加载连接的设备,如下所示:

Kotlin

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

Java

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

Java

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

如果需要,您还可以从哈希映射中获取迭代器,并处理每台设备 按 1:

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
}

获取与设备通信的权限

您的应用必须先获得 用户。

注意:如果您的应用使用 intent 过滤器来发现已连接的 USB 设备,它会自动接收 权限。否则,您必须请求 权限。

在某些情况下,您可能需要明确请求权限,例如, 应用枚举已连接且想要与之通信的 USB 设备 一个。在尝试与设备通信之前,您必须先检查是否具有访问设备的权限。如果 否则,如果用户拒绝授予访问设备的权限,您会收到运行时错误消息。

要明确获取权限,请先创建一个广播接收器。此接收器监听 当您调用 requestPermission() 时获取广播的 intent。调用 requestPermission() 会显示一个对话框,用于 用户请求允许连接到设备。以下示例代码展示了如何 创建广播接收器:

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

要注册广播接收器,请在 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), PendingIntent.FLAG_IMMUTABLE)
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), PendingIntent.FLAG_IMMUTABLE);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);

要显示对话框以向用户请求连接到设备的权限,请调用 requestPermission() 方法:

Kotlin

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

Java

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

当用户回复该对话框时,您的广播接收器会收到包含 EXTRA_PERMISSION_GRANTED extra,是一个布尔值 代表答案。在连接到 设备。

与设备通信

与 USB 设备的通信可以是同步的,也可以是异步的。无论是哪种情况 应创建一个新线程来执行所有数据传输,这样您就不会阻塞 界面线程。要正确设置与设备的通信,您需要获取相应的 UsbInterfaceUsbEndpoint 您要通过 UsbDeviceConnection 在此端点进行通信并发送请求的设备。通常,您的代码应该执行以下操作:

  • 检查 UsbDevice 对象的属性,例如产品 ID、 供应商 ID 或设备类别来确定您是否要 设备。
  • 当您确定要与该设备通信时, UsbInterface,以便与 该接口的相应 UsbEndpoint。接口可以有一个 通常具有用于双向访问的 输入和输出端点 通信。
  • 找到正确的端点后,打开 UsbDeviceConnection 访问该端点
  • 使用 bulkTransfer()controlTransfer() 方法提供要在端点上传输的数据。您应该 在另一个线程中执行此步骤以防止阻塞主界面线程。有关 有关在 Android 中使用线程的信息,请参阅进程和 线程

以下代码段是执行同步数据传输的一种简单方式。您的代码 应具有更多逻辑,以正确找到要进行通信的正确接口和端点 此外,还应在与主界面线程不同的线程中执行数据转移:

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

如需异步发送数据,请使用 UsbRequest 类对异步请求执行 initializequeue 操作,然后等待结果 与requestWait()共享。

终止与设备的通信

与设备通信完毕或设备断开连接后,请通过以下方式关闭 UsbInterfaceUsbDeviceConnection: 调用 releaseInterface()close()。如需监听分离事件,请执行以下操作: 创建如下所示的广播接收器:

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

在应用内(而不是清单中)创建广播接收器,即可 让应用仅在运行时处理断开连接事件。这样,分离事件 只会发送到当前正在运行的应用,而不会广播到所有应用。