前言
接触到的扫描设备分为三类:1)-usb或其它方式转串口,常规的打开读取串口即可;2)-usb模拟键盘输入模式,这种模式底层不是串口;3)原生串口模式,最简单,但这种接口普通手机没有。
本文只介绍第二种USB键入模式,通过USB设备查找、匹配、打开、数据读这个流程,可以先于并阻止安卓系统获得内容,就是说如果是后台进程这样读取后,安卓输入框中不会显示扫描到的内容
设备特点
USB接入方式、即插即用不需要驱动、可以在任意输入框内扫描并“输入”字符串、最后回车。例如,打开一个文本编辑器并处于活动状态,刷卡后可以看到就像键盘输入了一样,得到10位长度的卡号、回车。
就是说这种设备的接入一般是不需要编程的,但是问题就在于它输出的内容也无法控制,就像是用户在通过一个键盘输入。
拦截这种设备需求的产生
如果你想在“用户输入”的内容上做一些处理怎么办?要么是响应键盘输入事件,要么是像本文一样直接拦截USB数据流。
先看一下运行效果
(注意:这里有个USB安全提示,一般设备都是无法去除的,在订制机或者拥有系统签名的情况下才能去除)
流程核心代码:
1,获得USB管理器:
usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
if (usbManager == null) {
this.mainHandler.obtainMessage(MainHandler.WHAT_SHOW_MESSAGE, "无法获得USB服务").sendToTarget();
return false;
}
2,匹配特定的设备:
//获得所有USB设备
HashMap<String, UsbDevice> usbDeviceMap = usbManager.getDeviceList();
if (usbDeviceMap.size() == 0) {
this.mainHandler.obtainMessage(MainHandler.WHAT_SHOW_MESSAGE, "找不到USB设备").sendToTarget();
return false;
}
for (final UsbDevice device : usbDeviceMap.values()) {
Log.d(tag, "USB:pid=" + device.getProductId() + " vid=" + device.getVendorId());
if (USB_DEV_PID == device.getProductId() && USB_DEV_VID == device.getVendorId()) {
this.mainHandler.obtainMessage(MainHandler.WHAT_SHOW_MESSAGE, "已找到USB读卡器").sendToTarget();
targetUsb = device;
break;
}
}
3,打开USB连接
usbDeviceConnection = usbManager.openDevice(targetUsb);
if (usbDeviceConnection == null) {
mainHandler.obtainMessage(MainHandler.WHAT_USB_ERROR, "打开USB设备失败").sendToTarget();
return;
}
UsbInterface usbInterface = targetUsb.getInterface(0);
if (!usbDeviceConnection.claimInterface(usbInterface, true)) {
mainHandler.obtainMessage(MainHandler.WHAT_USB_ERROR, "不是已知协议的USB设备,无法使用").sendToTarget();
return;
}
4,开始读取
ArrayList<Byte> listBytes = new ArrayList<>();
byte[] buffer = new byte[maxPackageSize];
UsbEndpoint inEndpoint = usbInterface.getEndpoint(0);
mainHandler.obtainMessage(MainHandler.WHAT_READING, "读取准备就绪,请刷卡").sendToTarget();
deviceReading = true;
while (!Thread.interrupted()) {
int ret = usbDeviceConnection.bulkTransfer(inEndpoint, buffer, buffer.length, 200);
if (ret > 0) {
byte[] reab = new byte[ret];
System.arraycopy(buffer, 0, reab, 0, ret);
//Log.d(tag, "读取到:" + bytesToHexString(reab));
//字节流断句
for (byte ab : reab) {
if (ab == (byte) 0x28) {
if (listBytes.size() > 0) {
Byte[] lineBytes = listBytes.toArray(new Byte[0]);
getCarCodeBytes(ArrayUtils.toPrimitive(lineBytes));
listBytes.clear();
}
} else if (ab != (byte) 0x00) {
listBytes.add(ab);
}
}
}
}
5,解析字节原理
读取到的字节为USB-键值,对照表如下(设备不会输出其它值,故只列出相关部分):
1E Keyboard 1
1F Keyboard 2
20 Keyboard 3
21 Keyboard 4
22 Keyboard 5
23 Keyboard 6
24 Keyboard 7
25 Keyboard 8
26 Keyboard 9
27 Keyboard 0
6,解析方法:
private void decode(byte[] bytes) {
//转换时,可以直接一一对照,我这里用简单的减法
StringBuilder intString = new StringBuilder();
for (byte ab : bytes) {//按字节循环
//如果0x27,由于它的10进制是39,减去29是10,所以单独处理
if (ab == (byte) 0x27) intString.append(0);
//其它都是化为10进制后减去起始值29得到对应值
else intString.append(ByteTools.byteToInt(ab) - 29);
}
String hex = Long.toHexString(Long.parseLong(intString.toString()));
//TODO 获得字符串后继续其它业务
}
硬件及安卓源码
完整项目下载见我的资源《《USB读卡器安卓源码》》。
测试需要设备
1,读卡器可以到TB搜索USB读卡器,或者直接扫描我图片里的二维码,找类似的就行,只要是这种方式的都一样。
2,公头小口转母口大口的USB,TYPE-c或者MicroUsb的就看测试用的手机了。
文章写的有点糙,因为是两个版本的原因,改来改去都不满意,最后直接上全部代码,Android Studio项目整体压缩的,直接打开就行了。
注意,使用USB设备时如果没有系统权限,需要申请USB使用权限。