Android 连接USB设备(主机模式)

Posted 三杯五岳

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 连接USB设备(主机模式)相关的知识,希望对你有一定的参考价值。

Android 连接USB设备(配件模式)

两种模式(主机、配件)

首先,要了解android 连接USB设备主要通过两种模式, USB 配件模式、 USB 主机模式。

两种模式支持各种 USB 外围设备和 Android USB 配件(实现 Android 配件协议的硬件)。

在 USB 配件模式下,外部 USB 硬件充当 USB 主机。配件示例可能包括机器人控制器、扩展坞、诊断和音乐设备、自助服务终端、读卡器等等。这样,不具备主机功能的 Android 设备就能够与 USB 硬件互动。Android USB 配件必须设计为与 Android 设备兼容,并且必须遵守 Android 配件通信协议

在 USB 主机模式下,Android 设备充当主机。设备示例包括数码相机、键盘、鼠标和游戏控制器。针对各类应用和环境设计的 USB 设备仍可与能够与设备正常通信的 Android 应用互动。

图 1 展示了这两种模式之间的差异。当 Android 设备处于主机模式时,它会充当 USB 主机并为总线供电。当 Android 设备处于 USB 配件模式时,所连接的 USB 硬件(本例中为 Android USB 配件)充当主机并为总线供电。

图 1. USB 主机和配件模式

Android 3.1(API 级别 12)或更高版本的平台直接支持 USB 配件和主机模式。USB 配件模式还作为插件库向后移植到 Android 2.3.4(API 级别 10)中,以支持更广泛的设备。设备制造商可以选择是否在设备的系统映像中添加该插件库。

注意:是否支持 USB 主机和配件模式最终取决于设备的硬件,而与平台级别无关。您可以通过 <uses-feature> 元素过滤出支持 USB 主机和配件的设备。如需了解详情,请参阅 USB 配件主机文档。

调试注意事项

调试使用 USB 配件或主机功能的应用时,您很可能需要将 USB 硬件连接到 Android 设备。这样,您将无法通过 USB 将 adb 连接到 Android 设备。您仍可通过网络连接访问 adb。要通过网络连接启用 adb,请执行以下操作:

  1. 通过 USB 将 Android 设备连接到计算机。
  2. 在 SDK platform-tools/ 目录中,在命令提示符下输入 adb tcpip 5555
  3. 输入 adb connect <device-ip-address>:5555。您现在应该已连接到 Android 设备,并且可以发出常规的 adb 命令(如 adb logcat)。
  4. 要设置设备来监听 USB,请输入 adb usb

USB 主机模式

当您的 Android 设备处于 USB 主机模式时,它会充当 USB 主机,为总线供电并枚举连接的 USB 设备。Android 3.1 及更高版本支持 USB 主机模式。

API 概览

在开始前,请务必了解您需要使用的类。下表介绍了 android.hardware.usb 软件包中的 USB 主机 API。

表 1. USB 主机 API

说明
UsbManager您可以枚举连接的 USB 设备并与之通信。
UsbDevice表示连接的 USB 设备,并包含用于访问其标识信息、接口和端点的方法。
UsbInterface表示 USB 设备的接口,它定义设备的一组功能。设备可以具有一个或多个用于通信的接口。
UsbEndpoint表示接口端点,是此接口的通信通道。一个接口可以具有一个或多个端点,并且通常具有用于与设备进行双向通信的输入和输出端点。
UsbDeviceConnection表示与设备的连接,可在端点上传输数据。借助此类,您能够以同步或异步方式反复发送数据。
UsbRequest表示通过 UsbDeviceConnection 与设备通信的异步请求。
UsbConstants定义与 Linux 内核的 linux/usb/ch9.h 中的定义相对应的 USB 常量。

在大多数情况下,您需要在与 USB 设备通信时使用所有这些类(只有在进行异步通信时才需要 UsbRequest)。一般来说,您需要获取 UsbManager 才能检索所需的 UsbDevice。当您有了设备后,需要找到相应的 UsbInterface 和该接口的 UsbEndpoint 以进行通信。获得正确的端点后,打开 UsbDeviceConnection 以与 USB 设备通信。

Android 清单要求

下表介绍了您需要向应用的清单文件中添加哪些内容,才能使用 USB 主机 API:

  • 由于并非所有 Android 设备都保证支持 USB 主机 API,因此请添加 <uses-feature> 元素来声明您的应用使用 android.hardware.usb.host 功能。
  • 将应用的最低 SDK 设置为 API 级别 12 或更高级别。USB 主机 API 在更早的 API 级别中不存在。
  • 如果您希望应用接收有关连接的 USB 设备的通知,请为主 Activity 中的 android.hardware.usb.action.USB_DEVICE_ATTACHED Intent 指定 <intent-filter> 和 <meta-data> 元素对。<meta-data> 元素指向外部 XML 资源文件,用于声明有关要检测的设备的标识信息。

    在 XML 资源文件中,为要过滤的 USB 设备声明 <usb-device> 元素。下表介绍了 <usb-device> 的属性。一般来说,如果您想过滤某个特定设备,请使用供应商 ID 和产品 ID;如果您想过滤一组 USB 设备(例如大容量存储设备或数码相机),请使用类、子类和协议。您可以指定所有这些属性,也可以不指定任何属性。如果不指定任何属性,则会与每个 USB 设备进行匹配,因此只在应用需要时才这样做:

    • vendor-id
    • product-id
    • class
    • 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 设备连接到 Android 设备时,Android 系统可以确定您的应用是否对连接的设备感兴趣。如果是,您可以根据需要设置与该设备的通信。为此,您的应用必须执行以下操作:

  1. 发现连接的 USB 设备,具体方法是使用 Intent 过滤器在用户连接 USB 设备时接收通知,或者枚举已连接的 USB 设备。
  2. 请求用户授予连接到 USB 设备的权限(如果尚未获得权限)。
  3. 通过在适当的接口端点读取和写入数据来与 USB 设备通信。

发现设备

您的应用可以通过使用 Intent 过滤器在用户连接 USB 设备时接收通知,或者枚举已连接的 USB 设备来发现设备。如果您希望应用自动检测所需的设备,那么使用 Intent 过滤器就非常有用。如果您想获取所有连接的设备的列表,或者您的应用没有 Intent 过滤器,那么枚举连接的 USB 设备就非常有用。

使用 Intent 过滤器

要让您的应用发现特定的 USB 设备,您可以指定一个 Intent 过滤器,用于过滤 android.hardware.usb.action.USB_DEVICE_ATTACHED Intent。除了此 Intent 过滤器外,您还需要指定一个资源文件来指定 USB 设备的属性,例如产品 ID 和供应商 ID。当用户连接与您的设备过滤器匹配的设备时,系统会向其显示一个对话框,询问他们是否要启动您的应用。如果用户接受,您的应用会自动获得访问设备的权限,直到设备断开连接。

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

    <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() 方法获取连接的所有 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
    
    

获取与设备通信的权限

您的应用必须获得用户的许可,才能与 USB 设备通信。

注意:如果您的应用使用 Intent 过滤器来发现已连接的 USB 设备,则它会在用户允许应用处理 Intent 时自动获得权限。否则,您必须在应用中明确请求权限,然后才能连接到设备。

某些情况下可能需要明确请求权限,例如应用枚举已连接的 USB 设备,然后要与其中一个设备通信时。在尝试与设备通信之前,您必须先检查是否具有访问设备的权限。否则,如果用户拒绝授予访问设备的权限,您会收到运行时错误消息。

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

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

    UsbDevice device;
    ...
    usbManager.requestPermission(device, permissionIntent);
    

当用户在该对话框中作出回复时,您的广播接收器会收到包含 EXTRA_PERMISSION_GRANTED extra 的 Intent,即表示回答的布尔值。在连接到设备之前,请检查此 extra 的值是否为 true。

与设备通信

与 USB 设备的通信可以是同步的,也可以是异步的。在这两种情况下,您都应该创建一个新线程来执行所有数据传输,这样就不会锁定界面线程。要正确设置与设备的通信,您需要获取要与之通信的设备的相应 UsbInterface 和 UsbEndpoint,并使用 UsbDeviceConnection 在此端点发送请求。通常,您的代码应该执行以下操作:

  • 检查 UsbDevice 对象的属性(例如产品 ID、供应商 ID 或设备类别),判断您是否要与设备通信。
  • 在您确定要与设备通信时,找到您要用于通信的适当 UsbInterface 以及该接口的适当 UsbEndpoint。接口可以具有一个或多个端点,并且通常具有用于双向通信的输入和输出端点。
  • 找到正确的端点后,在该端点上打开 UsbDeviceConnection
  • 使用 bulkTransfer() 或 controlTransfer() 方法提供要在端点上传输的数据。您应该在另一个线程中执行此步骤,以防止主界面线程被锁定。如需详细了解如何在 Android 中使用线程,请参阅进程和线程

以下代码段是执行同步数据传输的一种简单方式。您的代码应具有更多逻辑,以便正确地找到要进行通信的接口和端点,并且还应在与主界面线程不同的另一个线程中进行数据传输:

    private Byte[] bytes;
    private static int TIMEOUT = 0;
    private boolean forceClaim = true;

    ...

    UsbInterface intf = device.getInterface(0);
    UsbEndpoint endpoint = intf.getEndpoint(0);
    UsbDeviceConnection connection = usbManager.openDevice(device);
    connection.claimInterface(intf, forceClaim);
    connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
    

要异步发送数据,请使用 UsbRequest 类以 initialize 和 queue 异步请求,然后使用 requestWait() 等待结果。

如需了解详情,请参阅 AdbTest 示例(展示了如何执行异步批量传输)和 MissileLauncher 示例(展示了如何异步监听中断端点)。

终止与设备的通信

当完成与设备的通信或者设备断开连接后,请调用 releaseInterface() 和 close() 来关闭 UsbInterface 和 UsbDeviceConnection。要监听断开连接事件,请创建如下所示的广播接收器:

    BroadcastReceiver usbReceiver = 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
                
            
        
    ;
    

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

以上是关于Android 连接USB设备(主机模式)的主要内容,如果未能解决你的问题,请参考以下文章

USB Accessory 模式

Android开启OTG功能/USB Host API功能

适用于标准类兼容 USB 设备的 Android USB 主机模式“软模式”驱动程序

从 USB 连接的 Android 移动设备访问 PC 的本地主机

虚拟机外接USB设备情况的vMotion问题

STM32F7 发现 - USB FS 主机/设备模式检测