Android 连接USB设备(主机模式)
Posted 三杯五岳
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 连接USB设备(主机模式)相关的知识,希望对你有一定的参考价值。
Android 连接USB设备(配件模式)
两种模式(主机、配件)
首先,要了解android 连接USB设备主要通过两种模式, USB 配件模式、 USB 主机模式。
两种模式支持各种 USB 外围设备和 Android USB 配件(实现 Android 配件协议的硬件)。
在 USB 配件模式下,外部 USB 硬件充当 USB 主机。配件示例可能包括机器人控制器、扩展坞、诊断和音乐设备、自助服务终端、读卡器等等。这样,不具备主机功能的 Android 设备就能够与 USB 硬件互动。Android USB 配件必须设计为与 Android 设备兼容,并且必须遵守 Android 配件通信协议。
在 USB 主机模式下,Android 设备充当主机。设备示例包括数码相机、键盘、鼠标和游戏控制器。针对各类应用和环境设计的 USB 设备仍可与能够与设备正常通信的 Android 应用互动。
图 1 展示了这两种模式之间的差异。当 Android 设备处于主机模式时,它会充当 USB 主机并为总线供电。当 Android 设备处于 USB 配件模式时,所连接的 USB 硬件(本例中为 Android USB 配件)充当主机并为总线供电。
Android 3.1(API 级别 12)或更高版本的平台直接支持 USB 配件和主机模式。USB 配件模式还作为插件库向后移植到 Android 2.3.4(API 级别 10)中,以支持更广泛的设备。设备制造商可以选择是否在设备的系统映像中添加该插件库。
注意:是否支持 USB 主机和配件模式最终取决于设备的硬件,而与平台级别无关。您可以通过 <uses-feature>
元素过滤出支持 USB 主机和配件的设备。如需了解详情,请参阅 USB 配件和主机文档。
调试注意事项
调试使用 USB 配件或主机功能的应用时,您很可能需要将 USB 硬件连接到 Android 设备。这样,您将无法通过 USB 将 adb
连接到 Android 设备。您仍可通过网络连接访问 adb
。要通过网络连接启用 adb
,请执行以下操作:
- 通过 USB 将 Android 设备连接到计算机。
- 在 SDK
platform-tools/
目录中,在命令提示符下输入adb tcpip 5555
。 - 输入
adb connect <device-ip-address>:5555
。您现在应该已连接到 Android 设备,并且可以发出常规的adb
命令(如adb logcat
)。 - 要设置设备来监听 USB,请输入
adb usb
。
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 常量。 |
在大多数情况下,您需要在与 USB 设备通信时使用所有这些类(只有在进行异步通信时才需要 UsbRequest
)。一般来说,您需要获取 UsbManager
才能检索所需的 UsbDevice
。当您有了设备后,需要找到相应的 UsbInterface
和该接口的 UsbEndpoint
以进行通信。获得正确的端点后,打开 UsbDeviceConnection
以与 USB 设备通信。
Android 清单要求
下表介绍了您需要向应用的清单文件中添加哪些内容,才能使用 USB 主机 API:
- 由于并非所有 Android 设备都保证支持 USB 主机 API,因此请添加
<uses-feature>
元素来声明您的应用使用android.hardware.usb.host
功能。 - 将应用的最低 SDK 设置为 API 级别 12 或更高级别。USB 主机 API 在更早的 API 级别中不存在。
- 如果您希望应用接收有关连接的 USB 设备的通知,请为主 Activity 中的
android.hardware.usb.action.USB_DEVICE_ATTACHED
Intent 指定<intent-filter>
和<meta-data>
元素对。<meta-data>
元素指向外部 XML 资源文件,用于声明有关要检测的设备的标识信息。在 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
中,并指定应过滤具有指定属性的所有 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 设备通信。
发现设备
您的应用可以通过使用 Intent 过滤器在用户连接 USB 设备时接收通知,或者枚举已连接的 USB 设备来发现设备。如果您希望应用自动检测所需的设备,那么使用 Intent 过滤器就非常有用。如果您想获取所有连接的设备的列表,或者您的应用没有 Intent 过滤器,那么枚举连接的 USB 设备就非常有用。
使用 Intent 过滤器
要让您的应用发现特定的 USB 设备,您可以指定一个 Intent 过滤器,用于过滤 android.hardware.usb.action.USB_DEVICE_ATTACHED
Intent。除了此 Intent 过滤器外,您还需要指定一个资源文件来指定 USB 设备的属性,例如产品 ID 和供应商 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
:
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
枚举设备
如果应用有兴趣在运行时检查当前连接的所有 USB 设备,则可以枚举总线上的设备。使用 getDeviceList()
方法获取连接的所有 USB 设备的哈希映射。如果要通过映射获取设备,哈希映射会由 USB 设备的名称进行键控。
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice device = deviceList.get("deviceName");
如果需要,您还可以从哈希映射中获取迭代器,并逐个处理每个设备:
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 设备,然后要与其中一个设备通信时。在尝试与设备通信之前,您必须先检查是否具有访问设备的权限。否则,如果用户拒绝授予访问设备的权限,您会收到运行时错误消息。
要明确获取权限,请先创建广播接收器。此接收器监听在您调用 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()
方法中添加以下命令:
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()
方法:
UsbDevice device;
...
usbManager.requestPermission(device, permissionIntent);
当用户在该对话框中作出回复时,您的广播接收器会收到包含 EXTRA_PERMISSION_GRANTED
extra 的 Intent,即表示回答的布尔值。在连接到设备之前,请检查此 extra 的值是否为 true。
与设备通信
与 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
;
在应用内(而不是清单中)创建广播接收器后,您的应用可以仅在运行时处理断开连接的事件。这样,断开连接事件只会发送到当前正在运行的应用,而不会广播到所有应用。
以上是关于Android 连接USB设备(主机模式)的主要内容,如果未能解决你的问题,请参考以下文章
适用于标准类兼容 USB 设备的 Android USB 主机模式“软模式”驱动程序