USB Accessory 概述
USB 附件模式允许用户连接专为 Android 设备供电的 USB 主机硬件。附件必须遵守 Android 附件开发套件文档中概述的 Android 附件协议。这使无法充当USB主机的 Android 设备仍可以与 USB 硬件进行交互。当由 Android 驱动的设备处于 USB 附件模式时,所连接的 Android USB 附件将充当主机,为 USB 总线供电,并枚举连接的设备。Android 3.1(API级别12)支持 USB 附件模式,并且该功能还反向移植到 Android 2.3.4(API级别10),以支持更广泛的设备。
一、选择正确的 USB Accessory API
尽管 USB 附件 API 在 Android 3.1 中引入了平台,但在 Android 2.3.4 中也可以使用 Google API 附加库使用它们。由于这些 API 是使用外部库向后移植的,因此您可以导入两个软件包以支持 USB 附件模式。根据您要支持的 Android 设备,您可能不得不使用另一种设备:
-
com.android.future.usb:为了在Android 2.3.4中支持USB附件模式,Google API附加库包括向后移植的USB附件API,这些API包含在此命名空间中。 Android 3.1还支持导入和调用此命名空间中的类,以支持使用附加库编写的应用程序。这个附加库是围绕android.hardware.usb的附件API,并且不支持USB主机模式。如果要支持最广泛的支持USB附件模式的设备,请使用附加库并导入此软件包。重要的是要注意,并非所有Android 2.3.4设备都需要支持USB附件功能。每个设备制造商都决定是否支持此功能,这就是为什么必须在清单文件中声明它的原因。 -
android.hardware.usb:该名称空间包含在Android 3.1中支持USB附件模式的类。该软件包是框架API的一部分,因此Android 3.1支持USB附件模式,而无需使用附加库。如果您只关心具有USB附件模式的硬件支持的Android 3.1或更高版本的设备(可在清单文件中声明),请使用此软件包。
安装Google API附加库
如果要安装插件,可以通过使用SDK Manager安装Google API Android API 10软件包来进行。有关安装附件库的更多信息,请参见安装Google API附件。
二、API 概述
由于附加库是框架API的包装,因此支持USB附件功能的类相似。即使您使用附加库,也可以使用 android.hardware.usb 的参考文档。
Note: 但是,您应该注意,加载项库和框架API之间的用法差异很小。
下表描述了支持USB附件API的类: Class Description UsbManager 允许您枚举并与连接的USB附件进行通信。 UsbAccessory 表示USB附件,并包含访问其标识信息的方法。
附加库和平台API之间的用法差异
使用Google API附加库和平台API有两个用法差异。如果使用附加库,则必须以以下方式获取 UsbManager 对象:
- java code
UsbManager manager = UsbManager.getInstance(this);
- kotlin code
val manager = UsbManager.getInstance(this)
如果不使用附加库,则必须以以下方式获取UsbManager对象:
- java code
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
- kotlin code
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
当使用意图过滤器过滤连接的附件时,UsbAccessory对象包含在传递给应用程序的意图内。如果使用附加库,则必须通过以下方式获取UsbAccessory对象:
- java code
UsbAccessory accessory = UsbManager.getAccessory(intent);
- kotlin code
val accessory = UsbManager.getAccessory(intent)
如果不使用附加库,则必须以以下方式获取 UsbAccessory 对象:
- java code
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
- kotlin code
val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory
三、Android manifest 要求
下表描述了使用USB附件API之前需要添加到应用程序清单文件中的内容。清单和资源文件示例显示如何声明以下项目:
-
由于并非保证所有支持 Android 的设备都支持 USB accessory API,因此请包含一个
<uses-feature>元素,该元素声明您的应用程序使用android.hardware.usb.accessory功能。 -
如果您正在使用 add-on library,请添加
<uses-library>元素,为该库指定com.android.future.usb.accessory。 -
如果您使用附加库,则将应用程序的最低SDK设置为API级别10;如果使用
android.hardware.usb软件包,则将其设置为API Level 10。 -
如果希望通知您的应用程序已连接的USB附件,请在主要 activity 中为
android.hardware.usb.action.USB_ACCESSORY_ATTACHEDintent 指定<intent-filter>和<meta-data>元素对。<meta-data>元素指向一个外部XML资源文件,该文件声明有关您要检测的附件的标识信息。 在XML资源文件中,声明要过滤的附件的<usb-accessory>元素。每个<usb-accessory>可以具有以下属性:- manufacturer
- model
- version
将资源文件保存在
res / xml /目录中。资源文件名(不带.xml扩展名)必须与您在<meta-data>元素中指定的名称相同。 XML资源文件的格式也显示在下面的示例中。
清单和资源文件示例
以下示例显示了一个清单清单及其对应的资源文件:
<manifest ...>
<uses-feature android:name="android.hardware.usb.accessory" />
<uses-sdk android:minSdkVersion="<version>" />
...
<application>
<uses-library android:name="com.android.future.usb.accessory" />
<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/accessory_filter" />
</activity>
</application>
</manifest>
在这种情况下,以下资源文件应保存在 res/xml/accessory_filter.xml 中,并指定应过滤具有相应型号,制造商和版本的任何附件。 附件将这些属性发送到Android设备:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>
四、accessories 使用
当用户将USB附件连接到Android设备时,Android系统可以确定您的应用程序是否对连接的附件感兴趣。 如果是这样,您可以根据需要设置与附件的通信。 为此,您的应用程序必须:
- 1.通过使用可过滤附件附件事件的意图过滤器或枚举连接的附件并找到合适的附件,可以发现连接的附件。
- 2.如果尚未获得与附件通信的权限,请询问用户。
- 3.通过在适当的接口端点上读写数据来与附件进行通信。
发现 accessory
您的应用程序可以通过使用意图过滤器在用户连接附件时通知您,也可以枚举已连接的附件来发现附件。 如果您希望应用程序能够自动检测所需的附件,则使用意图过滤器很有用。 如果要获取所有已连接附件的列表,或者您的应用程序没有针对意图进行过滤,则枚举已连接附件非常有用。
使用意图过滤器 intent filter
要让您的应用发现特定的USB附件,您可以指定一个意图过滤器以过滤 android.hardware.usb.action.USB_ACCESSORY_ATTACHED 意图。 与此意图过滤器一起,您需要指定一个资源文件,该文件指定USB附件的属性,例如制造商,型号和版本。 当用户连接与您的附件过滤器匹配的附件时,以下示例显示如何声明意图过滤器:
<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/accessory_filter" />
</activity>
以下示例显示如何声明相应的资源文件,该文件指定您感兴趣的USB附件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>
在您的活动中,您可以从这样的意图(使用附加库)中获取代表附件的 UsbAccessory :
- java code
UsbAccessory accessory = UsbManager.getAccessory(intent);
- kotlin code
val accessory = UsbManager.getAccessory(intent)
或这样(使用平台API):
- java code
UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
- kotlin code
val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory
Enumerate accessories
您可以让您的应用程序枚举在您的应用程序运行时已识别出自己的配件。 使用 getAccessoryList() 方法获取所有连接的USB附件的阵列:
- java code
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAccessoryList();
- kotlin code
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val accessoryList: Array<out UsbAccessory> = manager.accessoryList
Note:一次仅支持一个连接的附件。
获得与 accessory 通讯的权限
在与USB附件通信之前,您的应用程序必须先获得用户的许可。
Note:如果您的应用程序使用意图过滤器在连接的附件上发现它们,那么如果用户允许您的应用程序处理意图,它将自动获得许可。 否则,必须先在应用程序中明确请求权限,然后才能连接到附件。
在某些情况下,例如当您的应用程序枚举已连接的附件,然后想要与之通信时,可能需要明确征求许可。 在尝试与附件通信之前,必须检查是否具有访问附件的权限。 否则,如果用户拒绝访问附件的权限,您将收到运行时错误。
要明确获得许可,请首先创建一个广播接收器。 当您调用 requestPermission() 时,此接收器侦听要广播的意图。 调用 requestPermission() 会向用户显示一个对话框,询问是否允许连接到附件。 以下示例代码显示了如何创建广播接收器:
- java code
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);
}
}
}
}
};
- kotlin code
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 accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
accessory?.apply {
//call method to set up accessory communication
}
} else {
Log.d(TAG, "permission denied for accessory $accessory")
}
}
}
}
}
要注册广播接收器,请将其放在您的活动的 onCreate()方法中:
- java code
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);
- kotlin code
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)
要显示询问用户权限以连接到附件的对话框,请调用 requestPermission() 方法:
- java code
UsbAccessory accessory;
...
usbManager.requestPermission(accessory, permissionIntent);
- kotlin code
lateinit var accessory: UsbAccessory
...
usbManager.requestPermission(accessory, permissionIntent)
当用户答复对话框时,您的广播接收器会收到包含 EXTRA_PERMISSION_GRANTED 多余内容的意图,该意图是代表答案的布尔值。在连接至附件之前,请多检查此值是否为true。
与 accessory 通讯
您可以通过使用 UsbManager 获取附件来与附件进行通信,该文件描述符可以设置输入和输出流以对描述符进行数据读写。 这些流表示附件的输入和输出批量端点。 您应该在另一个线程中设置设备与附件之间的通信,因此不要锁定主UI线程。 以下示例显示了如何打开与之通信的附件:
- java code
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, this, "AccessoryThread");
thread.start();
}
}
- kotlin code
private lateinit var accessory: UsbAccessory
private var fileDescriptor: ParcelFileDescriptor? = null
private var inputStream: FileInputStream? = null
private var outputStream: FileOutputStream? = null
...
private fun openAccessory() {
Log.d(TAG, "openAccessory: $mAccessory")
fileDescriptor = usbManager.openAccessory(accessory)
fileDescriptor?.fileDescriptor?.also { fd ->
inputStream = FileInputStream(fd)
outputStream = FileOutputStream(fd)
val thread = Thread(null, this, "AccessoryThread")
thread.start()
}
}
在线程的 run() 方法中,可以使用 FileInputStream 或 FileOutputStream 对象读取和写入附件。 从带有 FileInputStream 对象的附件中读取数据时,请确保使用的缓冲区足够大以存储USB数据包数据。 Android附件协议最多支持 16384个字节 的数据包缓冲区,因此,为简单起见,您可以选择始终声明缓冲区为该大小。
Note:在较低级别,USB全速附件的数据包为64字节,USB高速附件的数据包为512字节。 为了简化,Android附件协议将两种速度的数据包捆绑在一起,形成一个逻辑数据包。
有关在Android中使用线程的更多信息,请参阅 Processes and Threads
终止与 accessory 的通讯
当您完成与附件的通信或附件已分离时,请通过调用 close() 关闭打开的文件描述符。要侦听分离的事件,请创建如下的广播接收器:
- java code
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
}
}
}
};
- kotlin code
var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (UsbManager.ACTION_USB_ACCESSORY_DETACHED == intent.action) {
val accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)
accessory?.apply {
// call your method that cleans up and closes communication with the accessory
}
}
}
}
在应用程序(而不是清单)中创建广播接收器,使您的应用程序只能在运行时处理分离事件。 这样,分离的事件仅发送到当前正在运行的应用程序,而不广播到所有应用程序。