官网解释:https://developer.android.com/guide/topics/connectivity/usb?hl=zh-cn
前言
Android中的两种USB模式,分别为主机模式、配件模式(也就是我们常说的从模式)。
- **主机模式:**在此模式下Android设备充当主机,并为总线供电。此时键盘、鼠标、U盘都属于外挂设备。
- **配件模式:**在此模式下所连接的Android USB设备会充当主机,为USB总线供电,并枚举所连接的设备。
主机模式
当您的 Android 设备处于 USB 主机模式时,它会充当 USB 主机,为总线供电并枚举连接的 USB 设备。Android 3.1 及更高版本支持 USB 主机模式。
API概览
在开发中用到的类如下:
- UsbManager:枚举连接的USB设备并与之通信
- UsbDevice:表示连接的USB设备,并包含用于访问其标识信息、接口和端点的方法。
- UsbInterface:表示USB设备的接口,它定义设备的一组功能,设备可以具有一个或者多个用于通信的接口。
- UsbEndpoint:接口端点,是此接口的通信通道。一个接口可以具有一个或多个端点,并且通常具有用于与设备进行双向通信的输入和输出端点。
- UsbDeviceConnection:表示与设备的连接,可在端点上上传数据。
- UsbRequest:通过UsbDeviceConnection与设备通信的异步请求。
- UsbConstants:定于与Linux内核的linux/usb/ch9.h中的定义相对于的USB常量。
通信的步骤如下:
- 通过USBManager获取所需的UsbDevice。
- 找到设备相应的UsbInterface和该接口的UsbEndpoint
- 找到正确的端点后,打开UsbDeviceConnection与USB设备通信。
清单文件配置
Manifest文件需要添加以下内容,才能使用USB主机API:
- 由于并不是所有的Android设备都保证支持USB主机API,因此需要添加
<uses-feature>
元素来声明应用使用android.hardware.usb.host
功能 - 在主Activity中的指定
<intent-filter>
和<meta-data>
元素对。其中<meta-data>
元素指向外部的XML资源文件,用于声明有关要检测的设备的标识信息 - 在XML文件中,声明
<usb-device>
元素,其中包括供应商ID和产品ID。如果想要过滤一组USB设备(例如大容量存储设备或者数码相机),请使用类、子类、和协议。这些属性可以指定,也可以不指定,如果不指定任何属性则会与每个USB设备进行匹配。
清单文件示例:
<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
文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
</resources>
发现设备
您的应用可以通过使用 Intent 过滤器在用户连接 USB 设备时接收通知,或者枚举已连接的 USB 设备来发现设备。如果您希望应用自动检测所需的设备,那么使用 Intent 过滤器就非常有用。如果您想获取所有连接的设备的列表,或者您的应用没有 Intent 过滤器,那么枚举连接的 USB 设备就非常有用。
使用Intent过滤器
在Manifest文件中声明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>
device_filter.xml内容如下:
<?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设备的哈希映射。
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice device = deviceList.get("deviceName");
如果需要,还可以从HashMap中获取迭代器,并逐个处理设备:
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设备通信。创建一个广播接收器,此接收器会在应用调用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);
与设备通信
与USB设备的通信可以是同步的,也可以是异步的。无论使用哪种方式,在开发过程中都应该创建一个新线程来执行所有数据传输。
- 检查UsbDevice对象的属性,判断是否要与设备通信
- 找到要用于通信的UsbInterface以及该接口的适当UsbEndpoint。
- 在该端点上打开UsbDeviceConnection
- 使用bulkTransfer()或controlTransfer()方法提供要在端点上传输的数据。
示例代码如下:
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
终止与设备的通信
当完成设备的通信或者设备断开之后,需要调用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总线供电,并枚举所连接的设备。
API概览
用到的两个类:
- UsbManager:允许您枚举所连接的 USB 配件并与之通信。
- UsbAccessory:表示一个 USB 配件,并包含访问其标识信息的方法。
清单文件配置
-
添加
<use-feature>
元素来声明应用使用android.hardware.usb.accessory
功能。 -
如果您希望应用收到 USB 配件连接通知,请为主 Activity 中的
android.hardware.usb.action.USB_ACCESSORY_ATTACHED
Intent 指定<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>
发现配件
您的应用可以通过两种方式发现配件:使用在用户连接配件时会收到通知的 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>
accessory_filter.xml文件的内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>
在Activity中通过intent来获取所连接配件的UsbAccessory
UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
枚举列表
通过UsbManager来枚举已连接的所有USB配件列表
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAccessoryList();
获取与配件通信的权限
应用必须获取用户的许可之后,才能与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) {
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);
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()函数:
UsbAccessory accessory;
...
usbManager.requestPermission(accessory, permissionIntent);
当用户对弹出的对话框做出操作时,广播接收器会收到包含EXTRA_PERMISSION_GRANTED
extra的intent,该extra是代表用户操作的布尔值。在连接到配件之前,需要检查此extra的值是否为true。
与配件通信
开发者可以使用UsbManager
来获取文件描述符,然后设置输入和输出流来向描述符读写数据,以此与配件通信。这些流表示配件的输入和输出批量端点。在通信的时候,需要注意:应该另启动一个线程来设置设备和配件的通信,防止主线程阻塞。示例代码如下:
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();
}
}
注意:在使用FileInputStream对象从配件读取数据时,请确保您使用的缓冲区足以存储USB数据包数据、Android配件协议支持最大16384字节的数据包缓冲区,因此为了简单期间,可以选择始终将缓冲区声明为此大小。
终止与配件通信
当完成与配件的通信后或者配件断开连接后,请调用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
}
}
}
};
建议点赞、收藏,方便以后查阅。