USB通信
通过获取UsbManager来检索所需的UsbDevice。有了设备后,您需要查找相应的UsbInterface及其UsbEndpoint进行通信后会正确的端点后,打开UsbDeviceConnection与USB设备通信。
UsbManager: 可以枚举USB设备并与之通信UsbDevice: 连接的USB设备,并包含用于访问其标识的方法信息,接口和端点。UsbInterface: 表示USB设备的接口,用于定义设备。设备可以是一个或多个用于通信的接口UsbEndpoint: 表示接口端点,是此接口的通信通道,一个接口可以有一个或多个端点,并且通常用于实现与设备的双向通信。UsbdeviceConnection: 表示与设备的连接,可在端点上传数据,允许同步或异步来回发送数据UsbRequest: 通过UsbDeviceConnection与设备通信的异步请求UsbConsants: 定义与 Linux 的 linux/usb/ch9.h 中的定义对应的 USB 常量 内核版本。
Usb 接口示例
- 详细的URL: https://developer.android.com/reference/android/hardware/usb/UsbManager
- 允许访问USB状态并与USB设备通信。
USB权限与特征声明
<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" />初始化USB通信
//设备管理器
private UsbManager usbManager;
//连接的设备
private UsbDevice currentDevice = null;
//连接状态类
private UsbDeviceConnection connection;
//USB连接接口
private UsbInterface usbInterface;
//请求权限
private String ACTION_USB_PERMISSION = "com.your.package.USB_PERMISSION";
private BroadcastReceiver mUsbBroadcast;
/**
* USB请求权限
*/
private class UsbBroadcast extends BroadcastReceiver {
public void onReceive(Context paramAnonymousContext, Intent paramAnonymousIntent) {
try {
String action = paramAnonymousIntent.getAction();
// 判断广播类型
if (ACTION_USB_PERMISSION.equals(action)) {
try {
// 如果用户同意,则对读写器进行操作
connectDevice();
} catch (Exception e) {
disconnectDevice();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}注册USB广播接收器
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(usbReceiver, filter);搜索USB设备
/**
* 获取Usb列表
* @return
*/
public List<UsbDevice> getUsbPrinters() {
List<UsbDevice> usbDevices = new ArrayList<>();
usbManager = (UsbManager) App.getContext().getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while (deviceIterator.hasNext()) {
UsbDevice device = deviceIterator.next();
if (isCscidDevice(device)) {
usbDevices.add(device);
}
}
return usbDevices;
}
/**
* 判断UsbDevice 是否为读卡器类型设备
* @param device
* @return true:读卡器设备
*/
public boolean isCscidDevice(UsbDevice device) {
if (device == null) {
return false;
}
if (device.getInterfaceCount() == 0) {
return false;
}
for (int i = 0; i < device.getInterfaceCount(); i++) {
android.hardware.usb.UsbInterface usbInterface = device.getInterface(i);
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
int productId = device.getProductId();
int vendorId = device.getVendorId();
// 此处根据自己设备的厂商ID以及设备ID来进行修改
if (productId == 10203 && vendorId == 5824) {
return true;
}
}
}
return false;
}连接设备
/**
* 连接设备
*/
public void connectDevice() {
try {
//权限请求
/*
* 在使用USB读写器设备前,应用必须获得权限。
* 为了确切地获得权限,首先需要创建一个广播接收器。在调用requestPermission()这个方法时从得到的广播中监听这个意图。
* 通过调用requestPermission()这个方法为用户跳出一个是否连接该设备的对话框。
*/
if (mUsbBroadcast == null) {
mUsbBroadcast = new UsbBroadcast();
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
App.getContext().registerReceiver(mUsbBroadcast, filter);
}
//获取全部设备
if (currentDevice == null) {
List<UsbDevice> usbDeviceList = getUsbPrinters();
if (usbDeviceList.size() > 0) {
currentDevice = usbDeviceList.get(0);
}
}
if (currentDevice != null) {
// 检查设备是否具有所需的权限
if (usbManager.hasPermission(currentDevice)) {
// 打开设备并进行通信
if (connection == null) {
connection = usbManager.openDevice(currentDevice);
}
if (connection != null) {
// 获取接口并配置
if (usbInterface == null) {
usbInterface = currentDevice.getInterface(0);
}
connection.claimInterface(usbInterface, true);
// 你可以在这里与设备进行通信
//readDataFromDevice();
Log.d(TAG, "连接到设备: " + currentDevice.getDeviceName());
} else {
Log.e(TAG, "无法打开设备: " + currentDevice.getDeviceName());
}
} else {
// 如果没有则请求权限
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(JuAiApp.getContext(), 0,
new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
usbManager.requestPermission(currentDevice, mPermissionIntent);
}
}
} catch (Exception r) {
r.printStackTrace();
}
}发送数据
UsbEndpoint.getType 获取USB 端点的传输类型
- UsbConstants.USB_ENDPOINT_XFER_CONTROL : 控制端点,忽悠啊用于设备的枚举(Enumeration),配置和状态查询
- UsbConstants.USB_ENDPOINT_XFER_BULK : 批量端点 ,主要用于传输大量对时间不敏感的数据,它依靠硬件重传机制来保证数据的完整性(100%可靠),但无法保证传输的带宽和延迟
- UsbConstants.USB_ENDPOINT_XFER_INT 中断端点,用于传输数量小,但对相应延迟有要求的数据,Host会周期性的轮询(Polling)该端点以获取数据,典型的输入设备HID,如USB鼠标,键盘,触摸屏,以及电源状态或特定的系统硬件中断事件上报。
- UsbConstants.USB_ENDPOINT_XFER_ISOC : 同步/等时端点,用于连续的,实时性极高的数据传输,它能保证稳定的带宽和发送速率,但没有重传机制(允许丢包) ,不保证数据的绝对完整性,UVC(USB摄像头) 视频流输出,UAC(USB声卡)音频数据等。
bulkTransfer 传输数据.
endpoint(UsbEndpoint): 与USB通信的事务端点buffer(bute[]): 用于发送或接受数据的缓冲区offset(int): 缓冲区中要发送或接受的第一个字节的索引(偏移量)length(int): 要发送或接受的数据长度。Build.VERSION_CODES.PAndroid 9 之前,大于16384字节的值 会被截断为 16384.Build.VERSION_CODES.P更高版本,任何长度值都是有效的。
- timeout(int): 超时时间(以毫秒为单位),0表示无限期等待。
public int bulkTransfer (UsbEndpoint endpoint,
byte[] buffer,
int offset,
int length,
int timeout)- 默认从头开始:该方法默认从buffer字节数组的索引0位置开始发送或接受数据
bulkTransfer(endpoint, buffer, length, timeout)- 添加了
offset参数,允许你精确指定从buffer数组的哪个索引位置开始传输数据。
bulkTransfer(endpoint, buffer, offset, length, timeout)- 读写 判断 根据
endpoint.getDirection()来判断数据的流向,发送与接受站在Android设备端来思考。- USB_DIR_OUT: 发送/写,发送是从
Host(Android 设备系统端) 流向Device (外部挂载的USB外设)。如果传入的端点是OUT端点,bulkTransfer就会将你提供的buffer里的数据发送给外设。 - USB_DIR_IN (接受/读):方向是从Device(外设)流向Host,如果传入的端点是IN端点,
bulkTransfer会进入监听状态,将外设发来的数据接受并填充到buffer中
- USB_DIR_OUT: 发送/写,发送是从
/**
* 发送数据
*
* @param message
*/
UsbEndpoint endpointOut = null; // 接口端点
private void sendDataToDevice(String message) {
// 获取输出端点
if (endpointOut == null) {
for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
UsbEndpoint endpoint = usbInterface.getEndpoint(i);
// 获取 USB传输 类型,同时输出方向 输出OUT
if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK &&
endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
endpointOut = endpoint; // 获取输出端点
break;
}
}
}
if (endpointOut == null || connection == null) {
Log.e(TAG, "输出端点或连接未初始化");
return;
}
byte[] buffer = message.getBytes(); // 将要发送的字符串转换为字节数组
//connection 是 连接设备之后 返回的 UsbDeviceConnection
// 在给定端点上执行一个传输
int bytesSent = connection.bulkTransfer(endpointOut, buffer, buffer.length, 1000); // 1秒超时
if (bytesSent < 0) {
Log.e(TAG, "发送数据失败");
} else {
Log.d(TAG, "成功发送数据: " + message);
}
}读取数据
/**
* 读取数据
*/
UsbEndpoint endpointIn = null;
private static final int BUFFER_SIZE = 1024; // 定义读取的缓冲区大小
private void readDataFromDevice() {
if (endpointIn == null) {
// 获取输入端点
for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
UsbEndpoint endpoint = usbInterface.getEndpoint(i);
Log.e(TAG, "readDataFromDevice: " + endpoint.getType());
Log.e(TAG, "getDirection: " + endpoint.getDirection());
// 获取中断数据端点
if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_INT &&
endpoint.getDirection() == UsbConstants.USB_DIR_IN) {
endpointIn = endpoint; // 获取输入端点
break;
}
}
}
if (endpointIn == null || connection == null) {
Log.e(TAG, "输入端点或连接未初始化");
return;
}
byte[] buffer = new byte[BUFFER_SIZE]; // 创建缓冲区
// 循环读取数据
Thread readThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
int bytesRead = connection.bulkTransfer(endpointIn, buffer, buffer.length, 1000); // 1秒超时
if (bytesRead > 0) {
// 处理读取到的数据
String data = new String(buffer, 0, bytesRead); // 将字节数组转换为字符串
Log.d(TAG, "读取数据: " + data);
} else if (bytesRead == 0) {
Log.d(TAG, "没有更多数据可读");
break;
} else {
Log.e(TAG, "读取数据失败: " + bytesRead);
break;
}
}
}
});
readThread.start(); // 启动读取线程
}断开设备并关闭资源
/**
* 断开设备
*/
public void disconnectDevice() {
try {
if (usbManager != null && currentDevice != null && usbInterface != null) {
// 获取之前连接的接口
// 释放接口
connection.releaseInterface(usbInterface);
// 关闭连接
connection.close();
// 清空连接
connection = null;
usbInterface = null;
//读取置空
endpointIn = null;
//写入置空
endpointOut = null;
//设备置空
currentDevice = null;
if (mUsbBroadcast != null) {
App.getContext().unregisterReceiver(mUsbBroadcast);
}
Log.d(TAG, "设备已断开: ");
}
} catch (Exception e) {
e.printStackTrace();
}
}USB协议相关
USB常见类型
//获取设备类型
for (int i = 0; i < device.getInterfaceCount(); i++) {
android.hardware.usb.UsbInterface usbInterface = device.getInterface(i);
/**
* 设备类 ID 设备类名称 描述
* 0x01 Audio 音频设备,如 USB 音频接口。
* 0x02 Communications 通信设备,如调制解调器和网络适配器。
* 0x03 HID (Human Interface Device) 人机接口设备,通常指键盘、鼠标等。
* 0x05 Physical Interface Device 物理接口设备,通常用于各种控制设备。
* 0x06 Image 图像设备,如 USB 摄像头和扫描仪。
* 0x07 Printer 打印机设备。
* 0x08 Mass Storage 大容量存储设备,如 USB 闪存驱动器和外部硬盘。
* 0x09 Hub USB 集线器。
* 0x0A CDC (Communications Device Class) 通信设备类,常用于调制解调器和串行口。
* 0x0B Smart ICCard 智能卡读卡器。
* 0x0D Content Security Device 内容保护设备,用于安全性应用。
* 0x0E Video 视频设备,如视频采集卡。
* 0xEF Miscellaneous 其他不属于上述类别的设备。
* 0xFF Vendor Specific 厂商自定义设备,通常特定于某个硬件厂商的设备。
* @param deviceClass
* @param deviceSubclass
* @param deviceProtocol
* @return
*/
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
int productId = device.getProductId();
int vendorId = device.getVendorId();
if (productId == 10203 && vendorId == 5824) {
return true;
}
}
}设备信息参数
//获取设备类型
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
//产品ID
int productId = device.getProductId();
//厂商ID
int vendorId = device.getVendorId();
if (productId == 10203 && vendorId == 5824) {
return true;
}
}
* DeviceName=/dev/bus/usb/005/007 USB端口名字
* ProductName=null 产品名字
* DeviceId=5007(0x138F) 设备ID
* VendorId=5824(0x16C0) 厂商ID
* ProductId=10203(0x27DB) 产品ID
* DeviceClass=0 设备分类
* device Class 为0-------------
* Interface.describeContents()=0 接口描述
* Interface.getEndpointCount()=2 接口端点数量
* Interface.getId()=0 ID接口
* Interface.getInterfaceClass()=3 接口数量
* anInterface.getInterfaceProtocol()=0 接口协议数量
* anInterface.getInterfaceSubclass()=0
* device Class 为0------end-------注意事项
- 扫码枪USB插拔时,会照成Activity重新加载,说明对Activity生命周期有影响,可以再Activity配置
android:configChanges="orientation|keyboard|keyboardHidden"扫码枪设备
USB键盘模式,自感模式(出厂默认): 大多数扫码枪是模拟键盘进行输入(不支持 包含有汉字的二维码),大多数时候需要使用带焦点的EditText 接受其扫描的信息,设置一个带焦点的EditText进行接受,此方式容易漏读扩展的ASCII码数据,除非二维码是标准的ASCII码(1-128)。由于扫码枪会将扫描出来的内容转化为键盘事件,对应的就是Android中的KeyEvent事件,所以我们只需要在我们的activity中重写dispatchKeyEvent方法,即可获取相应事件。- 通过
USB COM连接扫码:发送扫码开关指令进行扫码,即基于USB相关类控制扫码开关 USB串口自感模式:即串口连接(当扫码枪使用USB通讯接口,但主机应用程序是采用串口通讯方式接受数据),客户端只负责监听读取数据,无需控制扫码枪指令,需要依赖usb串口传输数据的第三方库。https://github.com/mik3y/usb-serial-for-android