USB 配件概览

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 配件 API

虽然 USB 配件 API 是在 Android 3.1 的平台上引入的,但它们 。因为这些 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:此命名空间包含支持 USB 的类 Android 3.1 中的“配件模式”功能。此软件包包含在框架 API 中,因此 Android 3.1 支持 USB 配件模式,无需使用插件库。使用此软件包 如果您只关心在硬件上支持 USB 的 Android 3.1 或更高版本 配件模式,您可以在清单文件中声明该模式。

安装 Google API 插件库

如果您想安装该插件,只需安装 Google API Android API 10 即可 SDK 管理器软件包。请参阅安装 Google API 插件,详细了解如何安装该插件库。

API 概览

由于该插件库是框架 API 的包装器,因此支持 USB 配件功能与此类似。即使您使用的是插件库,也可以使用 android.hardware.usb 的参考文档。

注意:不过,有一点 插件库和框架 API 之间的区别

下表介绍了支持 USB 配件 API 的类:

说明
UsbManager 允许您枚举所连接的 USB 配件并与之通信。
UsbAccessory 表示 USB 配件,并包含用于访问其标识的方法 信息。

插件库和平台 API 之间的使用差异

使用 Google API 插件库和使用平台之间存在两点使用差异 API。

如果使用插件库,则必须通过以下方式获取 UsbManager 对象:

Kotlin

val manager = UsbManager.getInstance(this)

Java

UsbManager manager = UsbManager.getInstance(this);

如果不使用插件库,则必须通过以下方式获取 UsbManager 对象:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);

当您使用 intent 过滤器过滤已连接的配件时,UsbAccessory 对象会包含在传递给您的 应用。如果使用插件库,则必须通过以下方式获取 UsbAccessory 对象:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

如果不使用插件库,则必须通过以下方式获取 UsbAccessory 对象:

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

Android 清单要求

以下列表介绍了您需要先向应用的清单文件中添加的内容,然后才能 支持使用 USB 配件 API。清单和资源文件 示例展示了如何声明这些项:

  • 由于并非所有 Android 设备都保证支持 USB 配件 API, 添加一个 <uses-feature> 元素,用于声明您的应用使用 android.hardware.usb.accessory 功能。
  • 如果您使用的是 插件库 添加 <uses-library> 元素,指定 com.android.future.usb.accessory 用于库。
  • 如果您使用的是插件库,请将应用的最低 SDK 设置为 API 级别 10 如果使用 android.hardware.usb 软件包,则为 12。
  • 如果您希望应用收到连接 USB 配件的通知,请指定 <intent-filter><meta-data> 元素对 android.hardware.usb.action.USB_ACCESSORY_ATTACHED intent。 <meta-data> 元素指向一个外部 XML 资源文件, 声明有关要检测的配件的标识信息。

    在 XML 资源文件中,声明 <usb-accessory> 元素 过滤组件。每个 <usb-accessory> 可以具有 以下属性:

    • manufacturer
    • model
    • version

    不建议按 version 进行过滤。配件 或设备不一定会(有意或无意)指定版本字符串。 当应用声明要过滤的版本属性以及配件或设备时 未指定版本字符串,这会导致 NullPointerException Android 的早期版本此问题已在 Android 12 中修复。

    将资源文件保存在 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,并指定具有 相应的型号、制造商和版本应进行过滤。配件会将这些 属性:

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>

使用配件

当用户将 USB 配件连接到 Android 设备时,Android 系统可以 确定您的应用是否对连接的配件感兴趣。如果是这样,您可以设置 与配件进行通信。为此,您的应用必须执行以下操作:

  1. 使用过滤配件的 intent 过滤器发现连接的配件 连接事件,或者枚举连接的配件并查找合适的配件。
  2. 请求用户授予与配件通信的权限(如果尚未获得) 。
  3. 通过在相应接口上读取和写入数据,与配件通信 端点。

发现配件

您的应用可以通过使用 intent 过滤器发现配件,该 intent 过滤器会在 用户连接配件,或枚举已连接的配件。使用 如果您希望应用自动检测 所需的配件。如果您想获取所有配件的列表,则枚举连接的配件非常有用。 连接配件上,或者您的应用未针对 intent 进行过滤。

使用 Intent 过滤器

要让您的应用发现特定 USB 配件,可以指定一个 intent 过滤器 来过滤 android.hardware.usb.action.USB_ACCESSORY_ATTACHED intent。正在 使用此 intent 过滤器时,您需要指定一个资源文件来指定 USB 接口的属性 例如制造商、型号和版本

以下示例展示了如何声明 Intent 过滤器:

<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>

在您的 activity 中,您可以获取代表UsbAccessory 从 intent 中连接配件,如下所示(使用插件库):

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

或通过以下方式获取(使用平台 API):

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

枚举配件

您可以让应用枚举在您访问网站时识别出的配件, 应用正在运行。

使用 getAccessoryList() 方法 以获取已连接的所有 USB 配件的数组:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val accessoryList: Array<out UsbAccessory> = manager.accessoryList

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAccessoryList();

注意 :以下设备上仅支持一个已连接的配件 。

获取与配件通信的权限

在与 USB 配件通信之前,您的应用程序必须获得您的 用户。

注意:如果您的应用使用 intent 过滤器来发现已连接的配件,它会自动接收 权限。否则,您必须请求 权限。

在某些情况下,您可能需要明确请求权限,例如, 应用枚举已连接且想要与之通信的配件 一个。在尝试与配件通信之前,您必须检查是否具有访问配件的权限。 否则,如果用户拒绝授予访问 配件。

要明确获取权限,请先创建一个广播接收器。此接收器监听 当您调用 requestPermission() 时获取广播的 intent。调用 requestPermission() 会显示一个对话框,用于 用户请求连接到配件的权限。以下示例代码展示了如何 创建广播接收器:

Kotlin

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")
                }
            }
        }
    }
}

Java

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);
                }
            }
        }
    }
};

要注册广播接收器,请将以下代码添加到 onCreate() 方法中, 活动:

Kotlin

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)

Java

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() 方法:

Kotlin

lateinit var accessory: UsbAccessory
...
usbManager.requestPermission(accessory, permissionIntent)

Java

UsbAccessory accessory;
...
usbManager.requestPermission(accessory, permissionIntent);

当用户回复该对话框时,您的广播接收器会收到包含 EXTRA_PERMISSION_GRANTED extra,是一个布尔值 代表答案。在连接到 配件。

与配件通信

您可以使用 UsbManager 与配件通信,以便 获取一个文件描述符,您可以设置输入流和输出流,以读取和写入数据 描述符。这些流表示配件的输入和输出批量端点。您应该将 在另一个线程中连接设备和配件之间的通信,这样就不会锁定 主界面线程。以下示例展示了如何打开配件以与之通信:

Kotlin

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()
    }
}

Java

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();
    }
}

在线程的 run() 方法中,您可以使用 FileInputStreamFileOutputStream 对象。朗读时 使用 FileInputStream 对象从配件获取数据,请确保 您使用的存储空间足以存储 USB 数据包数据。Android 配件协议支持 数据包缓冲区上限为 16384 字节,因此您可以选择始终将缓冲区声明为 以便简化操作

注意:在较低的级别,USB 的数据包为 64 个字节 全速配件和 512 字节的 USB 高速配件。Android 配件 为简单起见,协议将这两种速度的数据包捆绑到一个逻辑数据包中。

如需详细了解如何在 Android 中使用线程,请参阅进程和 线程

终止与配件的通信

与配件通信完毕后,或配件被拆下后,请关闭配件 是通过调用 close() 打开的文件描述符。 要监听断开连接事件,请创建如下所示的广播接收器:

Kotlin

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
            }
        }
    }
}

Java

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
            }
        }
    }
};

在应用内(而不是清单中)创建广播接收器,即可 让应用仅在运行时处理断开连接事件。这样,分离事件 只会发送到当前正在运行的应用,而不会广播到所有应用。