当 Android 设备运行在 USB 主机模式,它就像一个真正的 USB 主机,给 USB 总线供电并枚举所有连接的 USB 附件设备。USB 主机模式仅在 Android 3.1 及其更高版本的系统中被支持。
与 USB 主机相关的 API 都被保存在 android.hardware.usb 包中,相关类介绍如下表所示。
名称 | 作用 |
---|---|
UsbManager |
用于枚举连接的 USB 附件设备,并与 USB 附件进行通信。 |
UsbDevice | 表示一个连接的 USB 附件设备,包含访问该设备的标识信息、接口和端点的相关方法。 |
UsbInterface |
表示 USB 设备的一个接口,用于定义 USB 设备的一系列功能。一个设备可以有一个或者多个接口,这些接口可用于通信。 |
UsbEndpoint |
表示一个接口的端点,相当于接口通信的通道。 一个接口可以拥有一个或者多个端点,通常拥有输入和输出两个端点用于设备的双向通信。 |
UsbDeviceConnection | 表示一个到 USB 设备的连接,用于在端点上传输数据。这个类允许用户以同步或者非同步的方式双向传输数据。 |
UsbRequest | 表示一个通过 UsbDeviceConnection 与 USB 设备进行通信的异步请求。 |
UsbConstants | 定义了一些 USB 常量,这些常量与 Linux 内核中 linux/usb/ch9.h 的定义相同。 |
在大多数情况下,开发者需要使用上面提到的所有的类来与 USB 设备连接(UsbRequest 类只有在要求异步通信时才会被使用)。
通常情况下,开发者需要通过 UsbManager 实例去获取所需的 UsbDevice 实例,进而从 UsbDevice 实例中查找合适的 UsbInterface,并确定要用于通信的 UsbEndpoint,最后建立 UsbDeviceConnection 与 USB 设备的通信。
由于并不是所有的 Android 设备都支持 USB 附件 API,因此需要在应用程序的 Manifest 文件中使用 <uses-feature> 属性来声明当前的应用程序使用了 android. hardware.usb.host 特性。
设置最小 SDK 版本,该值应该大于 12,因为在早期的 Android SDK 版本中不支持 USB 主机模式。
如果需要当前应用程序在 USB 附件连接时获得通知,则应该在主 Activity 中为 android.hardware.usb.action.USB_ACCESSORY_ATTACHED Intent 指定 <intent-filter> 属性和 <meta-data> 属性对。其中,<meta-data> 属性指向一个外部的 XML 资源文件,其中包含要检测的附件的识别信息。
在 XML 资源文件中,通过 <usb-device> 属性为要过滤的 USB 附件定义相关信息,该属性可以包含 vendor-id、product-id、class、subclass、protocol(device or interface)等属性。
下面的示例代码演示如何定义 Manifest 文件:
<?xml version="1.0" encoding="utf-8"?>
<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 ACCESSORY ATTACHED"/>
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_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 系统会检测相关应用程序是否对连接的 USB 设备感兴趣。如果感兴趣,则会建立与 USB 设备的通信。要达到这个目的,应用程序应该能够完成以下三点。
1)使用 Intent Filter 发现设备的方式适合想让应用程序自动检测设备的情况。
实行这种方式需要在应用程序的 manifest 文件中为 Activity 添加 android.hardware.usb.action.USB_ ACCESSORY_ATTACHED Intent 的过滤功能,并通过 meta-data 属性指定对 USB 设备信息进行描述的 XML 文件。
Activity 设置的相关代码如下:
<activity ...>
...
<intent-filter>
<action android:name="android.hardware.usb.action.USB ACCESSORY ATTACHED"/>
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
资源文件 device_filter.xml 的内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="1234" product-id="5678"/>
</resources>
这样,当符合要求的 USB 设备被连接到 USB 主机上时,其产生的 Intent 对象就会被该 Activity 截获,并从中获取到代表 USB 设备的 UsbAccessory 对象。
2)枚举所有连接的 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
}
3)获取访问 USB 设备的权限。
在与 USB 设备建立通信之前,必须明确要向用户要求访问权限。通过调用 requestPermission() 方法向用户显示一个对话框,要求与设备建立连接的权限。
用户单击该对话框后会生成一个 Intent 对象并广播出去。因此,该应用程序需要创建一个 BroadcastReceiver 来接收该 Intent 对象,并从中获取用户授权。
创建 BroadcastReceiver 的相关示例代码如下:
private static final String ACTION_USB_PERMISSION=
"com.android.example.USB_PERMISSION";
private final BroadcastReceiver mUsbReceiver=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);
}
}
}
}
该 BroadcastReceiver 被创建后,应该在 Activity 的 onCreate() 方法中进行注册。相关代码如下:
UsbManager mUsbManager= (UsbManager) getSystemService (Context.USB_SERVICE);
private static final String ACTION_USB_PERMISSION=
"com.android.example.USB_PERMISSION";
...
mPermissionIntent=PendingIntent.getBroadcast (this, 0, new Intent (ACTION_USB_PERMISSION),0);
IntentFilter filter=new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver (mUsbReceiver, filter);
显示向用户要求访问设备权限的对话框,需要使用 requestPermission() 方法,代码如下:
UsbAccessory accessory;
...
mUsbManager.requestPermission(accessory, mPermissionIntent);
4)与 USB 设备进行通信。
与 USB 设备进行通信可以异步,也可以同步。无论在哪种情况下,与 USB 设备通信的过程都应该在一个单独的线程里被执行,以避免阻塞 UI 线程。
为了合理地创建与 USB 设备的通信,开发者需要获取要通信设备的适合的 UsbInterface 和 UsbEndpoint 对象,并在该端点上建立 UsbDeviceConnection 并发送通信请求。
总体来说,代码编写应该完成以下功能:
下面的代码演示了使用同步方式进行数据传输的过程,该示例仅用于演示,在真正的开发过程中,应该注意选择合适的接口和端点,并且在单独的线程中进行数据传输。
private Byte[] bytes
private static int TIMEOUT=0;
private boolean forceClaim=true;
...
UsbInterface intf=device.getInterface (0);
UsbEndpoint endpoint=intf.getEndpoint (0);
UsbDeviceConnection connection=mUsbManager.openDevice (device);
connection.claimInterface (intf, forceClaim);
connection.bulkTransfer (endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
进行异步数据传输使用 UsbRequest 类来初始化并将一个异步请求放入请求队列,然后调用 requestWait() 方法等待结果。
当与 USB 设备的通信结束或者设备被从系统中断开的时候,应该通过调用 releaseInterface() 方法和 close() 方法关闭 UsbInterface 和 UsbDeviceConnection。
下面的代码定义了一个监听设备被移除的事件的 BroadcastReceiver:
BroadcastReceiver mUsbReceiver=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_DEVICE);
if (accessory !=null) {
// call your method that cleans up and closes communication with the device
}
}
}
};
在应用程序中创建该 BroadcastReceiver,而不是在应用程序的 Manifest 配置文件中,这样可以保证只有 Activity 运行时才对 USB 设备断开事件进行处理。而设备断开事件只会被发送给当前运行的 Activity,而不是被广播给所有的应用程序。