当您的 Android 设备处于 USB 主机模式时,它充当 USB 主机,为总线供电并枚举连接的 USB 设备。Android 3.1 及更高版本支持 USB 主机模式。
API 概述
在开始之前,了解您需要使用的类非常重要。下表描述了 android.hardware.usb
包中的 USB 主机 API。
类 | 描述 |
---|---|
UsbManager |
允许您枚举连接的 USB 设备并与其通信。 |
UsbDevice |
表示连接的 USB 设备,并包含用于访问其标识信息、接口和端点的函数。 |
UsbInterface |
表示 USB 设备的接口,它定义了设备的一组功能。设备可以在一个或多个接口上进行通信。 |
UsbEndpoint |
表示接口端点,它是此接口的通信通道。接口可以具有一个或多个端点,通常具有输入和输出端点,用于与设备进行双向通信。 |
UsbDeviceConnection |
表示与设备的连接,它在端点上传输数据。此类允许您同步或异步发送数据。 |
UsbRequest |
表示通过 UsbDeviceConnection 与设备进行通信的异步请求。 |
UsbConstants |
定义了与 Linux 内核中的 linux/usb/ch9.h 中定义相对应的 USB 常量。 |
在大多数情况下,您需要在与 USB 设备通信时使用所有这些类(UsbRequest
仅在进行异步通信时才需要)。通常,您获取 UsbManager
来检索所需的 UsbDevice
。当您拥有该设备时,您需要找到合适的 UsbInterface
和该接口的 UsbEndpoint
以进行通信。获得正确的端点后,打开 UsbDeviceConnection
与 USB 设备通信。
Android 清单要求
以下列表描述了在使用 USB 主机 API 之前需要添加到应用程序清单文件中的内容
- 由于并非所有 Android 设备都保证支持 USB 主机 API,因此请包含一个
<uses-feature>
元素,声明您的应用程序使用android.hardware.usb.host
功能。 - 将应用程序的最低 SDK 设置为 API 级别 12 或更高。在早期 API 级别上不存在 USB 主机 API。
- 如果希望您的应用能够收到关于连接的 USB 设备的通知,请在主活动中为
<intent-filter>
和<meta-data>
元素对指定android.hardware.usb.action.USB_DEVICE_ATTACHED
意图。<meta-data>
元素指向一个外部 XML 资源文件,该文件声明了要检测的设备的标识信息。在 XML 资源文件中,为要过滤的 USB 设备声明
<usb-device>
元素。以下列表描述了<usb-device>
的属性。一般来说,如果要过滤特定设备,请使用供应商 ID 和产品 ID,如果要过滤一组 USB 设备(例如,大容量存储设备或数码相机),请使用类别、子类别和协议。您可以指定这些属性中的任何一个或所有属性。不指定任何属性会匹配所有 USB 设备,因此只有在您的应用需要此功能时才这样做。供应商 ID
产品 ID
类别
子类别
协议
(设备或接口)
将资源文件保存在
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
中,并指定应过滤任何具有指定属性的 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 系统可以确定您的应用是否对连接的设备感兴趣。如果是,则您可以根据需要设置与该设备的通信。为此,您的应用需要执行以下操作:
- 通过使用意图过滤器来发现连接的 USB 设备,以便在用户连接 USB 设备时收到通知,或者通过枚举已连接的 USB 设备来发现这些设备。
- 在尚未获得权限的情况下,向用户请求连接到 USB 设备的权限。
- 通过在相应的接口端点上读写数据来与 USB 设备通信。
发现设备
您的应用可以通过使用意图过滤器来发现 USB 设备(以便在用户连接设备时收到通知),或者通过枚举已连接的 USB 设备来发现这些设备。如果您希望应用能够自动检测目标设备,则使用意图过滤器非常有用。如果您希望获得已连接的所有设备的列表,或者您的应用没有过滤意图,则枚举已连接的 USB 设备非常有用。
使用意图过滤器
为了使您的应用能够发现特定的 USB 设备,您可以指定一个意图过滤器来过滤 android.hardware.usb.action.USB_DEVICE_ATTACHED
意图。除了此意图过滤器,您还需要指定一个资源文件,其中包含 USB 设备的属性,例如产品 ID 和供应商 ID。当用户连接的设备与您的设备过滤器匹配时,系统会向用户显示一个对话框,询问他们是否要启动您的应用。如果用户同意,您的应用会自动获得访问该设备的权限,直到该设备断开连接。
以下示例显示了如何声明意图过滤器。
<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)
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");
如果需要,您还可以直接从哈希映射中获取迭代器,并逐个处理每个设备。
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 }
获得与设备通信的权限
在与 USB 设备通信之前,您的应用必须获得用户的权限。
注意:如果您的应用 使用意图过滤器 来发现连接的 USB 设备,则如果用户允许您的应用处理意图,它会自动获得权限。如果不是,您必须在连接到设备之前在应用中显式请求权限。
在某些情况下可能需要显式请求权限,例如当您的应用枚举已连接的 USB 设备,然后希望与其中一个设备通信时。您必须在尝试与设备通信之前检查访问该设备的权限。如果不是,如果您拒绝了访问该设备的权限,则会收到运行时错误。
要显式获得权限,首先创建一个广播接收器。此接收器侦听在您调用 requestPermission()
时广播的意图。调用 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
附加数据的意图,该数据是一个表示答案的布尔值。在连接到设备之前,请检查此附加数据的 true 值。
与设备通信
与 USB 设备的通信可以是同步的,也可以是异步的。无论哪种情况,您都应该创建一个新的线程来执行所有数据传输,这样就不会阻塞 UI 线程。要正确设置与设备的通信,您需要获取要通信的设备的相应 UsbInterface
和 UsbEndpoint
,并在该端点上使用 UsbDeviceConnection
发送请求。一般来说,您的代码应该执行以下操作:
- 检查
UsbDevice
对象的属性(例如产品 ID、供应商 ID 或设备类别),以确定是否要与该设备通信。 - 当您确定要与该设备通信时,请查找要用于通信的相应
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 } } }
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
类来 初始化
和 排队
异步请求,然后使用 requestWait()
等待结果。
终止与设备的通信
当您完成与设备的通信,或者如果设备已断开连接,请通过调用 releaseInterface()
和 close()
来关闭 UsbInterface
和 UsbDeviceConnection
。要侦听断开连接事件,请创建以下广播接收器。
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 } } } };
在应用(而不是清单文件中)创建广播接收器,这样您的应用就可以只在运行时处理断开连接事件。这样,断开连接事件只会发送到当前运行的应用,而不是广播到所有应用。