Android NDK开发详解连接性之USB 主机概览
当 Android 设备处于 USB 主机模式时,它会充当 USB 主机,为总线供电,并枚举连接的 USB 设备。Android 3.1 及更高版本支持 USB 主机模式。
API 概览
在开始前,请务必了解您需要使用的类。下表介绍了 android.hardware.usb 软件包中的 USB 主机 API。
表 1. USB 主机 API
在大多数情况下,您需要在与 USB 设备通信时使用所有这些类(只有在进行异步通信时才需要 UsbRequest)。一般来说,您需要获取 UsbManager 来检索所需的 UsbDevice。当您有了设备后,需要找到要用于通信的相应接口的 UsbInterface 和 UsbEndpoint。获得正确的端点后,打开 UsbDeviceConnection 以与 USB 设备通信。
Android 清单要求
下表介绍了在使用 USB 主机 API 之前需要向应用的清单文件中添加的内容:
由于并非所有 Android 设备都保证支持 USB 主机 API,因此请添加 元素来声明您的应用使用 android.hardware.usb.host 功能。
将应用的最低 SDK 设置为 API 级别 12 或更高级别。USB 主机 API 在早期的 API 级别中不存在。
如果您希望应用在已连接的 USB 设备时收到通知,请为主 activity 中的 android.hardware.usb.action.USB_DEVICE_ATTACHED intent 指定 和 元素对。 元素指向一个外部 XML 资源文件,该文件用于声明有关要检测的设备的标识信息。
在 XML 资源文件中,为您要过滤的 USB 设备声明 元素。以下列表介绍了 的属性。一般来说,如果您要过滤特定设备,请使用供应商 ID 和产品 ID;如果要过滤一组 USB 设备(例如大容量存储设备或数码相机),请使用类、子类和协议。您可以指定所有这些属性,也可以不指定任何属性。不指定任何属性会匹配每个 USB 设备,因此请仅在您的应用需要时才这样做:
vendor-id
product-id
class
subclass
protocol(设备或接口)
将资源文件保存在 res/xml/ 目录中。资源文件名(不带 .xml 扩展名)必须与您在 元素中指定的文件名相同。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 设备,具体方法是:使用 intent 过滤器在用户连接 USB 设备时收到通知;或者枚举已连接的 USB 设备。
请求用户授予连接到 USB 设备的权限(如果尚未获得权限)。
通过在适当的接口端点上读取和写入数据,与 USB 设备进行通信。
发现设备
您的应用可以使用以下两种方式来发现 USB 设备:使用 intent 过滤器在用户连接设备时接收通知,或枚举已连接的 USB 设备。如果您希望应用能够自动检测所需的设备,则使用 intent 过滤器将非常有用。如果您想获取所有已连接设备的列表,或者您的应用没有针对 intent 进行过滤,那么枚举连接的 USB 设备就非常有用。
使用 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 中,您可以从 intent 获取代表已连接设备的 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 设备通信。
注意:如果您的应用使用 intent 过滤器来发现连接的 USB 设备,则当用户允许您的应用处理 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);
}
}
}
}
};
如需注册广播接收器,请在您的 Activity 的 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)
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);
要显示对话框以向用户请求连接到设备的权限,请调用 requestPermission() 方法:
Kotlin
lateinit var device: UsbDevice
...
usbManager.requestPermission(device, permissionIntent)
Java
UsbDevice device;
...
usbManager.requestPermission(device, permissionIntent);
当用户回复该对话框时,您的广播接收器会收到包含 EXTRA_PERMISSION_GRANTED extra 的 intent,该 extra 是一个表示答案的布尔值。在连接到设备之前,请检查此 extra 的值是否为 true。
与设备通信
与 USB 设备的通信可以是同步的,也可以是异步的。无论是哪种情况,您都应该创建一个新线程来执行所有数据传输,以免阻塞界面线程。为了正确设置与设备的通信,您需要获取要通信的设备的相应 UsbInterface 和 UsbEndpoint,并使用 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 类对异步请求执行 initialize 和 queue 操作,然后使用 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
}
}
}
};
在应用内(而不是清单中)创建广播接收器,可让您的应用仅在运行时处理分离事件。这样,分离事件将仅发送到当前正在运行的应用,而不会广播到所有应用。
本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2023-11-11。