Android USB
通信2020-09-07
[email protected]
Android
通过 USB
配件和 USB
主机两种模式支持各种 USB
外围设备和 Android USB
配件(实现 Android
配件协议的硬件)。
在 USB
配件模式下,外部 USB
硬件充当 USB
主机,配件包括机器人控制器、扩展坞、音乐设备、自助服务终端、读卡器等。不具备主机功能的 Android
设备就能够与 USB
硬件互动。Android USB
配件必须设计为与 Android
设备兼容,并且必须遵守 Android
配件通信协议。
在 USB
主机模式下,Android
设备充当主机,设备包括数码相机、键盘、鼠标和游戏控制器等。针对各类应用和环境设计的 USB
设备仍可与能够与设备正常通信的 Android
应用互动。
下图展示了这两种模式之间的差异。
当 Android
设备处于主机模式时,它会充当 USB 主机并为总线供电。
当 Android
设备处于 USB
配件模式时,所连接的 USB
硬件(本例中为 Android USB
配件)充当主机并为总线供电。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OuBVIJUv-1605706806773)(https://developer.android.com/images/usb-host-accessory.png)]
Android 3.1
(API 级别 12)或更高版本的平台直接支持 USB 配件和主机模式。USB 配件模式还作为插件库向后移植到 Android 2.3.4
(API 级别 10)中,以支持更广泛的设备。设备制造商可以选择是否在设备的系统映像中添加该插件库。
USB
通信模式简称:Accessory Mode
: 配件模式,以下简称 AM
Host Mode
: 主机模式,以下简称 HM
USB
设备管理器:
Android 2.3.4
版本:
UsbManager manager = UsbManager.getInstance(this);
Android 3.1
版本:
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
USB AM
介绍:获取已连接的配件(通过清单文件结合IntentFilter
获取):
Android 2.3.4
版本:
UsbAccessory accessory = UsbManager.getAccessory(intent);
Android 3.1
版本:
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
配置 AndroidManifest.xml
:
Android 2.3.4
版本:
添加
元素来声明您的应用使用 com.android.future.usb.accessory
功能
<uses-feature android:name="com.android.future.usb.accessory" android:required="true"/>
<uses-sdk android:minSdkVersion="10" />
Android 3.1
版本:
添加
元素来声明您的应用使用 android.hardware.usb.accessory
功能
<uses-feature android:name="android.hardware.usb.accessory" android:required="true"/>
<uses-sdk android:minSdkVersion="12" />
如果需要动态监听 USB
配件的连接或断开,需要在 Activity
的标签中制定
和
元素。其中,
元素指向一个外部 XML
资源文件,该文件位于 res/xml/
路径下,声明了关于要检测的配件的识别信息。该 XML
资源文件中,为要过滤的配件声明
元素。每个
可以包含属性:manufacturer、model 、version
。
activity 标签配置示例:
...
型号、制造商、版本信息的 xml
文件如:
获取已连接的所有 USB 配件的列表
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAccessoryList();
AM
通信开发指南:发现配件:
有两种方式发现配件:使用在用户连接配件时会收到通知的 Intent
过滤器,或者获取已连接的配件列表。
如果需要应用能够自动检测所需的配件,那么使用 Intent
过滤器会很便捷。
如果需要获得所有已连接配件的列表,或者您的应用没有 Intent
过滤器,那么枚举已连接的配件很便捷。
使用配件:当用户将 USB
配件连接到 Android
设备时,Android
系统可以确定您的应用是否对所连接的配件感兴趣。如果感兴趣,您可以根据需要设置与该配件的通信。为此,您的应用必须执行以下操作:
发现连接的配件,方法是使用 Intent
过滤器来过滤配件连接事件,或者枚举所连接的配件并找到适合的配件。
请求用户授予与配件通信的权限(如果尚未获得该权限)。
通过在适当的接口端点读取和写入数据来与配件通信。
获取与配件通信的权限:
要明确获取权限,请先创建一个广播接收器。此接收器监听在您调用 requestPermission()
时接收广播的 Intent
。调用 requestPermission()
会向用户显示一个对话框,请求授予连接到配件的权限。以下示例代码展示了如何创建广播接收器:
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) {
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if(accessory != null) {
// call method to set up accessory communication
}
} else {
Log.d(TAG, "permission denied for accessory " + accessory);
}
}
}
}
};
注册广播接收器,请将以下代码添加到您的 Activity
中的 onCreate()
方法中:
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);
要显示对话框来向用户请求连接到配件的权限,请调用 requestPermission()
方法:
UsbAccessory accessory;
if (BUILD.VERSION.SDK_INIT >= 10 && BUILD.VERSION.SDK_INIT < 12) {
accessory = UsbManager.getAccessory(intent);
} else {
accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
}
usbManager.requestPermission(accessory, permissionIntent);
当用户在该对话框中作出回复时,您的广播接收器会收到包含 EXTRA_PERMISSION_GRANTED
extra
的 Intent
,该 extra
是代表用户回复的布尔值。在连接到配件之前,请检查此 extra
的值是否为 true
。
与配件进行通信:
使用 UsbManager
来获取文件描述符,然后设置输入和输出流来向描述符读写数据,以此与配件通信。这些流表示配件的输入和输出批量端点。您应该在另一个线程中设置设备和配件之间的通信,这样就不会锁定主界面线程。在线程的 run()
方法中,您可以使用 FileInputStream
或 FileOutputStream
对象来读写配件。
使用 FileInputStream
对象从配件读取数据时,请确保您使用的缓冲区足以存储 USB
数据包数据。Android
配件协议支持最大 16384 字节的数据包缓冲区,因此为了简单起见,您可以选择始终将缓冲区声明为此大小。
在较低的 API
级别,USB
全速配件的数据包为 64 字节,USB
高速配件的数据包为 512 字节。为简单起见,Android
配件协议将全速和高速数据包打包到一个逻辑数据包中。
以下示例展示了如何打开配件以与之通信:
UsbAccessory accessory;
ParcelFileDescriptor fileDescriptor;
FileInputStream inputStream;
FileOutputStream outputStream;
...
private void openAccessory() {
Log.d(TAG, "openAccessory: " + accessory);
fileDescriptor = usbManager.openAccessory(accessory);
if (fileDescriptor != null) {
FileDescriptor fd = fileDescriptor.getFileDescriptor();
inputStream = new FileInputStream(fd);
outputStream = new FileOutputStream(fd);
Thread thread = new Thread(null, new Runnable() {
@override
public void run() {
// TODO
}
}, "AccessoryThread");
thread.start();
}
}
终止与配件通信:
当完成与配件的通信后或者配件断开连接后,请调用 close()
来关闭您打开的文件描述符。
在应用内(而不是清单中)创建广播接收器,可让应用仅在运行时才处理断开连接事件。这样,断开连接事件只会发送到当前正在运行的应用,而不会广播到所有应用。
要监听断开连接事件,请创建如下所示的广播接收器:
BroadcastReceiver usbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (accessory != null) {
// call your method that cleans up and closes communication with the accessory
}
}
}
};
USB HM
介绍:当您的 Android
设备处于 USB
主机模式时,它会充当 USB
主机,为总线供电并枚举连接的 USB
设备。Android 3.1
及更高版本支持 USB
主机模式。
在开始前,请务必了解您需要使用的类。下表介绍了 android.hardware.usb
软件包中的 USB
主机 API
:
类 | 说明 |
---|---|
UsbManager |
可以枚举连接的 USB 设备并与之通信。 |
UsbDevice |
表示连接的 USB 设备,并包含用于访问其标识信息、接口和端点的方法。 |
UsbInterface |
表示接口端点,是此接口的通信通道。一个接口可以具有一个或多个端点,并且通常具有用于与设备进行双向通信的输入和输出端点。 |
UsbDeviceConnection |
表示与设备的连接,可在端点上传输数据。借助此类,您能够以同步或异步方式反复发送数据。 |
UsbRequest |
表示通过 UsbDeviceConnection 与设备通信的异步请求。 |
UsbConstants |
定义与 Linux 内核的 linux/usb/ch9.h 中的定义相对应的 USB 常量。 |
在大多数情况下,您需要在与 USB
设备通信时使用所有这些类(只有在进行异步通信时才需要 UsbRequest
)。一般来说,您需要获取 UsbManager
才能检索所需的 UsbDevice
。当您有了设备后,需要找到相应的 UsbInterface
和该接口的 UsbEndpoint
以进行通信。获得正确的端点后,打开 UsbDeviceConnection
以与 USB
设备通信。
USB
设备:在 Activity
中,您可以通过以下方式从 Intent
获取代表所连接设备的 UsbDevice
:
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
配置 AndroidManifest.xml
:
添加
元素来声明您的应用使用 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/device_filter.xml
中,并指定应过滤具有指定属性的所有 USB 设备:
获取已连接的所有USB设备的列表:
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
// 获取到的是设备名与USB设备的映射
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while(deviceIterator.hasNext()) {
UsbDevice device = deviceIterator.next();
// your code
}
HM
通信开发指南:发现设备:
应用可以通过使用 Intent
过滤器在用户连接 USB
设备时接收通知,或者枚举已连接的 USB
设备来发现设备。
如果希望应用自动检测所需的设备,那么使用 Intent
过滤器就非常有用。
如果想获取所有连接的设备的列表,或者您的应用没有 Intent
过滤器,那么枚举连接的 USB
设备就非常有用。
使用设备:
USB
设备连接到 Android
设备时,Android
系统可以确定您的应用是否对连接的设备感兴趣。如果是,您可以根据需要设置与该设备的通信。为此,您的应用必须执行以下操作:
发现连接的 USB
设备,具体方法是使用 Intent
过滤器在用户连接 USB
设备时接收通知,或者枚举已连接的 USB
设备。
请求用户授予连接到 USB
设备的权限(如果尚未获得权限)。
通过在适当的接口端点读取和写入数据来与 USB
设备通信。
获取与USB设备通信的权限:
如果是通过清单获取固定的USB
设备,可在 Activity
中,通过以下方式从 Intent
获取代表所连接设备的 UsbDevice
:
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
要明确获取权限,请先创建广播接收器。此接收器监听在您调用 requestPermission()
时接收广播的 Intent
。调用 requestPermission()
会向用户显示一个对话框,请求连接到设备的权限。以下示例代码展示了如何创建广播接收器:
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()
方法中添加以下命令:
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);
要显示对话框以向用户请求连接到设备的权限,请调用 requestPermission()
方法,当用户在该对话框中作出回复时,您的广播接收器会收到包含 EXTRA_PERMISSION_GRANTED
extra
的 Intent
,即表示回答的布尔值。在连接到设备之前,请检查此 extra
的值是否为 true
。
void requestDevicePermission(UsbDevice device) {
usbManager.requestPermission(device, permissionIntent);
}
与设备通信:
与 USB
设备的通信可以是同步的,也可以是异步的。在这两种情况下,您都应该创建一个新线程来执行所有数据传输,这样就不会锁定界面线程。
要正确设置与设备的通信,您需要获取要与之通信的设备的相应 UsbInterface
和 UsbEndpoint
,并使用 UsbDeviceConnection
在此端点发送请求。通常,您的代码应该执行以下操作:
检查 UsbDevice
对象的属性(例如产品 ID
、供应商 ID
或设备类别),判断您是否要与设备通信。
在您确定要与设备通信时,找到您要用于通信的适当 UsbInterface
以及该接口的适当 UsbEndpoint
。接口可以具有一个或多个端点,并且通常具有用于双向通信的输入和输出端点。
找到正确的端点后,在该端点上打开 UsbDeviceConnection
。
使用 bulkTransfer()
或 controlTransfer()
方法提供要在端点上传输的数据。您应该在另一个线程中执行此步骤,以防止主界面线程被锁定。如需详细了解如何在 Android
中使用线程,请参阅进程和线程。
以下代码段是执行同步数据传输的一种简单方式。您的代码应具有更多逻辑,以便正确地找到要进行通信的接口和端点,并且还应在与主界面线程不同的另一个线程中进行数据传输:
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()
等待结果。
参考示例: AdbTest 示例(展示了如何执行异步批量传输)和 MissileLauncher 示例(展示了如何异步监听中断端点)。
终止通信:
releaseInterface()
和 close()
来关闭 UsbInterface
和 UsbDeviceConnection
。要监听断开连接事件,请创建如下所示的广播接收器: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
}
}
}
};
HostMode
的访问流: