Android NDK开发详解连接性之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 中为该平台引入的,但是它们在 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 管理器安装 Google API Android API 10 软件包。如需详细了解如何安装该插件库,请参阅安装 Google API 插件。

API 概览

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

注意:但是,您需要注意,插件库和框架 API 之间存在细微的使用差异。

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

在这里插入图片描述

插件库和平台 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 对象会包含在传递给应用的 intent 内。如果使用插件库,则必须通过以下方式获取 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,因此请添加 元素来声明您的应用使用 android.hardware.usb.accessory 功能。
如果您使用的是插件库,请添加 元素为该库指定 com.android.future.usb.accessory。
如果您使用插件库,请将应用的最低 SDK 设置为 API 级别 10;如果使用 android.hardware.usb 软件包,则将其设置为 12。
如果您希望应用收到 USB 配件连接通知,请在主 activity 中为 android.hardware.usb.action.USB_ACCESSORY_ATTACHED intent 指定 和 元素对。 元素指向外部 XML 资源文件,用于声明有关要检测的配件的标识信息。

在 XML 资源文件中,为要过滤的配件声明 元素。每个 都可以具有以下属性:

manufacturer
model
version
不建议在 version 上进行过滤。配件或设备有时可能不会指定版本字符串(无论是有意还是无意)。如果您的应用声明了要过滤的版本属性,并且配件或设备未指定版本字符串,则会导致较低 Android 版本上的 NullPointerException。此问题已在 Android 12 中得到解决。

将资源文件保存在 res/xml/ 目录中。资源文件名(不带 .xml 扩展名)必须与您在 元素中指定的文件名相同。下面的示例也显示了 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>

使用配件

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

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

发现配件

您的应用可以通过以下方法发现配件:使用 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 中,您可以从 intent 中获取代表所连接配件的 UsbAccessory,如下所示(使用插件库):

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 过滤器来发现连接的配件,则当用户允许您的应用处理 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);
                }
            }
        }
    }
};

如需注册广播接收器,请将以下代码添加到 activity 的 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 的 intent,该 extra 是一个表示答案的布尔值。在连接到配件之前,请检查此 extra 的值是否为 true。

与配件通信

您可以使用 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() 方法中,您可以使用 FileInputStream 或 FileOutputStream 对象对配件执行读写操作。当使用 FileInputStream 对象从配件读取数据时,请确保您使用的缓冲区足够大,以存储 USB 数据包数据。Android 配件协议支持最多 16384 字节的数据包缓冲区,因此,为简单起见,您可以选择始终声明此大小的缓冲区。

注意:在较低级别,USB 全速配件的数据包为 64 字节,USB 高速配件的数据包为 512 字节。为简单起见,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
            }
        }
    }
};

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

本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。

最后更新时间 (UTC):2023-11-12。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五一编程

程序之路有我与你同行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值