USB 主机模式概述
当你的安卓设备处于主机模式下,它就会充当主机,为总线提供电力支持,会枚举出与此主机相连的安卓设备。USB 主机模式支持 Android API 3.1或更高。
API 概述
在开始之前,很重要的是你要理解你即将使用到的那些类。下面的表格表述了在 android.hardware.usb 包下主机模式的API。
表1:主机模式API
类 | 描述 |
---|---|
USBManager | 允许你枚举出已连接的设备。 与设备进行通信。 |
UsbDevice | 表示一个已连接的USB设备。 包含了访问该USB设备的方法、设备的识别信息、接口、端点。 |
UsbInterface | 描述了一个USB设备的接口。该接口定义了这个设备的一套行为。 设备具有一个或多个,可在其上进行通信的接口。 |
UsbEndpoint | 描述了与一个接口通信的通道。 一个接口可以有一个或多个端点。 通常为了与设备进行双向通信,会有一个input端点,一个output端点。 |
UsbDeviceConnection | 表示一个USB的连接,此连接会在端点上给设备传输数据。 这个类允许你通过异步或非异步的方式,来回传输数据。 |
UsbRequest | 表示通过 UsbDeviceConnection 与进行异步通信的一个请求。 |
UsbConstants | 定义了 USB 的常量。 与 Linux 内核中的 linux/usb/ch9.h 一致。 |
* USBManager
* UsbDevice
* UsbInterface
* UsbEndpoint
* UsbDeviceConnection
* UsbRequest
* UsbConstants
大多数情况下,你要与一个 USB 设备进行通信,需要用到所有的类(只有要进行异步通讯时才需要使用到 UsbRequest )。通常会获得一个 UsbManager 来检索所需的 UsbDevice 。当你拿到设备对象后( UsbDevice
),你需要找到恰当的 UsbInterFace 以及该 UsbInterface
的 UsbEndpoint 来进行通信。当你获得正确的 endpoint
后,你就打开了一个与该 USB 设备进行通信的一个连接( UsbDeviceConnection )。
安卓清单要求(manifest文件)
下面列表描述了在使用 USB 主机模式 API 之前,你需要向你应用程序的清单文件中添加的内容。
- 因为不是所有的安卓设备都能够支持 USB 主机模式 API ,所以导入一个 <uses-feature> 元素声明你应用程序要使用
android.hardware.usb.host
功能。 - 设置应用程序适配的最小 SDK 为 API 12 或更高。更早的 API 版本不支持 USB 主机模式。
- 如果想让你的应用程序可以被一个 USB 设备依附,为
MainActivity
中的android.hardware.usb.action.USB_DEVICE_ATTACHED
意图指定一个 <intent-filter> 和 <meta-data> 的元素对。 <meta-data> 元素指向一个声明了你想要找到的设备的标识信息的外部 XML 资源文件。
在这个 XML 资源文件中,为了过滤你想要的 USB 设备,声明了 <usb-device> 元素。下面的列表描述了 <usb-device> 的属性。通常,如果你想要筛选一个特殊的设备,并使用类,子类和协议(如果要筛选一组 USB 设备,比如大容量存储设备或者数码相机)需要使用供应商和产品ID。那些属性是非必要的,指不指定都可以(根据需要即可)。没有属性匹配每个 USB 设备(比如,有的 USB 设备只有产品id和供应商id,没有 class 或者 subclass 什么的),因此只有在你的应用程序需要时才这样做:
vendor-id
:供应商idproduct-id
:产品idclass
:类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 设备 (扫码枪) 连接到 安卓设备(超市用的收银设备) 时,安卓系统能够决定你的应用程序是否对当前连接的设备感兴趣。如果是这样,你可以设置与你渴望的设备通信。要想这么做,你的应用应该:
- 通过使用意图过滤器(intent-filter) 来发现连接到的 USB 设备,以便在用户连接了 USB 设备时收到通知,或者枚举已连接的 USB 设备。
- 如果还没获得连接 USB 设备的权限,则向用户请求权限。
- 通过在相同的接口端点中读写数据与 USB 设备进行通信。
发现设备
当用户连接设备时,你的应用程序的意图过滤器会被通知,这样便可以发现 USB 设备,也可以枚举已经连接的 USB 设备。
- 如果你想让你的应用程序能够自动发现想要的设备,那么使用意图过滤器会很有用。
- 如果你想获得已连接设备的清单,或者你的应用不过滤任何意图,那么枚举已连接的设备会很有用。
使用意图过滤器
为了让你的应用可以发现某一个特定的 USB 设备,你可以指定一个意图过滤器来筛选 android.hardware.usb.action.USB_DEVIEC_ACTION
意图。与这个过滤器一起,你还需要指定一个目标 USB 设备所具有的属性的资源文件,例如:供应商id或产品id。 当用户连接了一个意图过滤器能够匹配的设备时,如果系统想要开启你的应用,那么会弹出一个对话框(dialog)向用户请求。如果用户同意,你的应用能够自动获得访问该设备的权限,直到该设备断开连接。
下面的例子展示了这么声明意图过滤器:
<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() 方法来获得存储所有已连接设备的 HashMap
。如果你想从这个 HashMap
中获得一个设备的话,这个 HashMap
是以设备的名字作为键(key) 的。
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice device = deviceList.get("deviceName");
如果需要,你也可以从 HashMap
中获得一个迭代器( iterator
)来一个一个处理每个设备:
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 设备时然后想要与其中一个进行通信。你必须在尝试与其通信前检查是否获得了访问设备的权限。如果没有没这么做,并且用户拒绝你访问设备的话,那你就会得到一个运行时的错误。
为了明确获得权限,首先要创建一个广播接收器。该广播接收器监听你调用 requestPermission 时发出的广播。调用 requestPermission 会展示一个对话框( dialog
) 向用户请求连接 USB 设备的权限。下面简单的代码展示了怎么创建这个广播接收器:
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);
}
}
}
}
};
要注册广播接收器,将下面的代码添加到 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);
要展示请求连接 USB 设备权限的对话框,调用如下的 requestPermission 方法:
UsbDevice device;
...
mUsbManager.requestPermission(device, mPermissionIntent);
当用户回应了该对话框,你的广播接收器会接受到一个额外包含了 EXTRA_PERMISSION_GRANTED 的意图(intent),这个 EXTRA_PERMISSION_GRANTED 就是用户操作对话框的结果,是一个 boolean
类型的值。在连接设备前,检查此额外值是否为 true 。
与设备通信
与 USB 设备通信可以使用同步的方式也可以使用异步的方式。在任意场景中,你需要创建一个进行所有数据传输的新线程,这样你才不会锁住 UI 线程 (安卓不推荐在 UI 线程中进行 I/O 操作)。要正确的与一个设备进行通信,你需要获得合适的 UsbInterface
和 UsbEndpoint
(你要使用 UsbDeviceConnection
在此 UsbEndpoint
进行发请求操作)。通常,你的代码应该:
- 检查
UsbDevice
对象的属性,例如 产品id,供应商id,或设备类 来确定你是否要与这个设备通信。 - 当你确定你就是要连接这个设备的时候,找到你要用来与之通信的
UsbInterface
和对应该UsbInterface
的UsbEndpoint
。接口可以有一个或多个端点,通常会输入端点和输出端点来支持两种通信方式。 - 当你找到了正确的端点,在该端点上打开一个
UsbDeviceConnection
连接。 - 你想在一个
endpoint
进行数据传输需要使用 bulkTransfer() 方法或者 controlTransfer() 方法。 不要在主线程中执行上述操作,防止造成 UI 线程阻塞。 要想查看关于安卓中使用线程的更多信息,请查看 Processes and Threads。
如下代码段是执行同步数据传输的一个简单的方法。你的代码应该具有更多的逻辑来正确地找到正确的接口和端点来进行通信, 并且应该在非 UI 线程中进行数据的传输 :
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);
//do in another thread
connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT);
要异步发送数据,使用 UsbRequest
类 来初始化并排队异步请求,然后等待 requestWait() 方法的返回结果。
要获得更多信息,查看 ADBTest sample ,这个示例展示了怎么实现异步的批量传输,还有 MissileLauncher sample ,这个示例展示了如何异步地监听中断端点。
结束与设备的通信
当你与设备的通信已经完成,或者设备已经断开连接,通过调研 releaseInterface() 和 close() 来关闭 UsbInterface
和 UsbDeviceConnection
。
创建如下的广播接受者,来监听分离(断开连接)事件:
BroadcastReceiver mUsbReceiver = 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
}
}
}
};
在应用程序中创建这个动态的广播接受者(不用在清单文件中注册,那是静态广播接受者),运行你的应用程序仅仅在运行时监听分离事件。这种途径,分离事件仅仅发送到当前正在运行的应用程序中,而不是发送给了所有的应用程序。
Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates.
上次更新日期:五月 24, 2018