Android USB
通信
- Author: 迷你小猪
- Date:
2020-09-07
- Email:
Benjie1017@outlook.com
知识简介:
-
Android
通过USB
配件和USB
主机两种模式支持各种USB
外围设备和Android USB
配件(实现Android
配件协议的硬件)。-
在
USB
配件模式下,外部USB
硬件充当USB
主机,配件包括机器人控制器、扩展坞、音乐设备、自助服务终端、读卡器等。不具备主机功能的Android
设备就能够与USB
硬件互动。Android USB
配件必须设计为与Android
设备兼容,并且必须遵守Android
配件通信协议。 -
在
USB
主机模式下,Android
设备充当主机,设备包括数码相机、键盘、鼠标和游戏控制器等。针对各类应用和环境设计的USB
设备仍可与能够与设备正常通信的Android
应用互动。
-
-
下图展示了这两种模式之间的差异。
-
当
Android
设备处于主机模式时,它会充当 USB 主机并为总线供电。 -
当
Android
设备处于USB
配件模式时,所连接的USB
硬件(本例中为Android USB
配件)充当主机并为总线供电。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OuBVIJUv-1605706806773)(https://developer.android.com/images/usb-host-accessory.png)]
-
-
Android 3.1
(API 级别 12)或更高版本的平台直接支持 USB 配件和主机模式。USB 配件模式还作为插件库向后移植到Android 2.3.4
(API 级别 10)中,以支持更广泛的设备。设备制造商可以选择是否在设备的系统映像中添加该插件库。
USB
通信模式简称:
-
Accessory Mode
: 配件模式,以下简称AM
-
Host Mode
: 主机模式,以下简称HM
-
USB
设备管理器:-
Android 2.3.4
版本:UsbManager manager = UsbManager.getInstance(this);
-
Android 3.1
版本:UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
-
USB AM
介绍:
获取指定的配件设备:
-
获取已连接的配件(通过清单文件结合
IntentFilter
获取):-
Android 2.3.4
版本:UsbAccessory accessory = UsbManager.getAccessory(intent);
-
Android 3.1
版本:UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
-
-
配置
AndroidManifest.xml
:-
Android 2.3.4
版本:-
添加
<uses-feature>
元素来声明您的应用使用com.android.future.usb.accessory
功能<uses-feature android:name="com.android.future.usb.accessory" android:required="true"/> <uses-sdk android:minSdkVersion="10" />
-
-
Android 3.1
版本:-
添加
<uses-feature>
元素来声明您的应用使用android.hardware.usb.accessory
功能<uses-feature android:name="android.hardware.usb.accessory" android:required="true"/> <uses-sdk android:minSdkVersion="12" />
-
-
-
如果需要动态监听
USB
配件的连接或断开,需要在Activity
的标签中制定<intent-filter>
和<meta-data>
元素。其中,<meta-data>
元素指向一个外部XML
资源文件,该文件位于res/xml/
路径下,声明了关于要检测的配件的识别信息。该XML
资源文件中,为要过滤的配件声明<usb-accessory>
元素。每个<usb-accessory>
可以包含属性:manufacturer、model 、version
。-
activity 标签配置示例:
<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>
-
型号、制造商、版本信息的
xml
文件如:<?xml version="1.0" encoding="utf-8"?> <resources> <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/> </resources>
-
获取设备列表:
-
获取已连接的所有 USB 配件的列表
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); UsbAccessory[] accessoryList = manager.getAccessoryList();
AM
通信开发指南:
-
发现配件:
-
有两种方式发现配件:使用在用户连接配件时会收到通知的
Intent
过滤器,或者获取已连接的配件列表。 -
如果需要应用能够自动检测所需的配件,那么使用
Intent
过滤器会很便捷。 -
如果需要获得所有已连接配件的列表,或者您的应用没有
Intent
过滤器,那么枚举已连接的配件很便捷。
-
-
使用配件:当用户将
USB
配件连接到Android
设备时,Android
系统可以确定您的应用是否对所连接的配件感兴趣。如果感兴趣,您可以根据需要设置与该配件的通信。为此,您的应用必须执行以下操作:-
发现连接的配件,方法是使用
Intent
过滤器来过滤配件连接事件,或者枚举所连接的配件并找到适合的配件。 -
请求用户授予与配件通信的权限(如果尚未获得该权限)。
-
通过在适当的接口端点读取和写入数据来与配件通信。
-
-
获取与配件通信的权限:
-
要明确获取权限,请先创建一个广播接收器。此接收器监听在您调用
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) { 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()
方法中:UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); registerReceiver(usbReceiver, filter);
-
要显示对话框来向用户请求连接到配件的权限,请调用
requestPermission()
方法:UsbAccessory accessory; if (BUILD.VERSION.SDK_INIT >= 10 && BUILD.VERSION.SDK_INIT < 12) { accessory = UsbManager.getAccessory(intent); } else { accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); } usbManager.requestPermission(accessory, permissionIntent);
-
当用户在该对话框中作出回复时,您的广播接收器会收到包含
EXTRA_PERMISSION_GRANTED
extra
的Intent
,该extra
是代表用户回复的布尔值。在连接到配件之前,请检查此extra
的值是否为true
。
-
-
与配件进行通信:
-
使用
UsbManager
来获取文件描述符,然后设置输入和输出流来向描述符读写数据,以此与配件通信。这些流表示配件的输入和输出批量端点。您应该在另一个线程中设置设备和配件之间的通信,这样就不会锁定主界面线程。在线程的run()
方法中,您可以使用FileInputStream
或FileOutputStream
对象来读写配件。 -
使用
FileInputStream
对象从配件读取数据时,请确保您使用的缓冲区足以存储USB
数据包数据。Android
配件协议支持最大 16384 字节的数据包缓冲区,因此为了简单起见,您可以选择始终将缓冲区声明为此大小。 -
在较低的
API
级别,USB
全速配件的数据包为 64 字节,USB
高速配件的数据包为 512 字节。为简单起见,Android
配件协议将全速和高速数据包打包到一个逻辑数据包中。 -
以下示例展示了如何打开配件以与之通信:
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, new Runnable() { @override public void run() { // TODO } }, "AccessoryThread"); thread.start(); } }
-
-
终止与配件通信:
-
当完成与配件的通信后或者配件断开连接后,请调用
close()
来关闭您打开的文件描述符。 -
在应用内(而不是清单中)创建广播接收器,可让应用仅在运行时才处理断开连接事件。这样,断开连接事件只会发送到当前正在运行的应用,而不会广播到所有应用。
-
要监听断开连接事件,请创建如下所示的广播接收器:
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 } } } };
-
USB HM
介绍:
-
当您的
Android
设备处于USB
主机模式时,它会充当USB
主机,为总线供电并枚举连接的USB
设备。Android 3.1
及更高版本支持USB
主机模式。 -
在开始前,请务必了解您需要使用的类。下表介绍了
android.hardware.usb
软件包中的USB
主机API
:类 说明 UsbManager
可以枚举连接的 USB 设备并与之通信。 UsbDevice
表示连接的 USB 设备,并包含用于访问其标识信息、接口和端点的方法。 UsbInterface
表示接口端点,是此接口的通信通道。一个接口可以具有一个或多个端点,并且通常具有用于与设备进行双向通信的输入和输出端点。 UsbDeviceConnection
表示与设备的连接,可在端点上传输数据。借助此类,您能够以同步或异步方式反复发送数据。 UsbRequest
表示通过 UsbDeviceConnection 与设备通信的异步请求。 UsbConstants
定义与 Linux 内核的 linux/usb/ch9.h 中的定义相对应的 USB 常量。 -
在大多数情况下,您需要在与
USB
设备通信时使用所有这些类(只有在进行异步通信时才需要UsbRequest
)。一般来说,您需要获取UsbManager
才能检索所需的UsbDevice
。当您有了设备后,需要找到相应的UsbInterface
和该接口的UsbEndpoint
以进行通信。获得正确的端点后,打开UsbDeviceConnection
以与USB
设备通信。
获取指定的 USB
设备:
-
在
Activity
中,您可以通过以下方式从Intent
获取代表所连接设备的UsbDevice
:UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
-
配置
AndroidManifest.xml
:-
添加
<uses-feature>
元素来声明您的应用使用android.hardware.usb.host
功能。 -
将应用的最低
SDK
设置为API
级别 12 或更高级别。USB
主机API
在更早的API
级别中不存在。<uses-feature android:name="android.hardware.usb.host" /> <uses-sdk android:minSdkVersion="12" />
-
-
如果您希望应用接收有关连接的
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(设备或接口)
-
以下示例展示了示例清单及其相应的资源文件:
<uses-feature android:name="android.hardware.usb.host" /> <uses-sdk android:minSdkVersion="12" /> ... <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>
-
在这种情况下,以下资源文件应保存在
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设备列表:
-
获取已连接的所有USB设备的列表:
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); // 获取到的是设备名与USB设备的映射 HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); while(deviceIterator.hasNext()) { UsbDevice device = deviceIterator.next(); // your code }
HM
通信开发指南:
-
发现设备:
-
应用可以通过使用
Intent
过滤器在用户连接USB
设备时接收通知,或者枚举已连接的USB
设备来发现设备。 -
如果希望应用自动检测所需的设备,那么使用
Intent
过滤器就非常有用。 -
如果想获取所有连接的设备的列表,或者您的应用没有
Intent
过滤器,那么枚举连接的USB
设备就非常有用。
-
-
使用设备:
- 当用户将
USB
设备连接到Android
设备时,Android
系统可以确定您的应用是否对连接的设备感兴趣。如果是,您可以根据需要设置与该设备的通信。为此,您的应用必须执行以下操作:-
发现连接的
USB
设备,具体方法是使用Intent
过滤器在用户连接USB
设备时接收通知,或者枚举已连接的USB
设备。 -
请求用户授予连接到
USB
设备的权限(如果尚未获得权限)。 -
通过在适当的接口端点读取和写入数据来与
USB
设备通信。
-
- 当用户将
-
获取与USB设备通信的权限:
-
如果是通过清单获取固定的
USB
设备,可在Activity
中,通过以下方式从Intent
获取代表所连接设备的UsbDevice
:UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
-
要明确获取权限,请先创建广播接收器。此接收器监听在您调用
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()
方法中添加以下命令:private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); registerReceiver(usbReceiver, filter);
-
要显示对话框以向用户请求连接到设备的权限,请调用
requestPermission()
方法,当用户在该对话框中作出回复时,您的广播接收器会收到包含EXTRA_PERMISSION_GRANTED
extra
的Intent
,即表示回答的布尔值。在连接到设备之前,请检查此extra
的值是否为true
。void requestDevicePermission(UsbDevice device) { usbManager.requestPermission(device, permissionIntent); }
-
-
与设备通信:
-
与
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 } } } };
- 在应用内(而不是清单中)创建广播接收器后,您的应用可以仅在运行时处理断开连接的事件。这样,断开连接事件只会发送到当前正在运行的应用,而不会广播到所有应用。
- 当完成与设备的通信或者设备断开连接后,请调用
HostMode
的访问流:
- 获取USB硬件访问权限;
- https://www.jianshu.com/p/e2e57cddac6a
- https://blog.csdn.net/shareus/article/details/50375204
- https://blog.csdn.net/zhoumushui/article/details/101304296
- https://blog.csdn.net/zhongshujunqia/article/details/53212548?locationNum=5&fps=1–
- https://www.cnblogs.com/AmyHu/p/10654500.html