最近做了一个关于Android设备Usb外接扫码器的项目,在此记录下。
言归正传,首先扫码器有这两种模式:USB HID-KBW,USB 虚拟串口。
USB HID-KBW:扫码器会将扫描出来的内容模拟成键盘事件,就是Android中就是KeyEvent里面对应的常量(比如:KeyEvent.KEYCODE_A 表示小写字母a,如果要表示大写字母A,就会组合KeyEvent.KEYCODE_SHIFT_LEFT或者KeyEvent.KEYCODE_SHIFT_RIGHT + KeyEvent.KEYCODE_A)。
这种模式优点是使用简单,只要有焦点的文本输入框,系统会自动将扫码出的内容填充到文本框内,缺点是不支持中文!USB 虚拟串口:这种模式相对来说复杂点,但也比较简单,Android系统屏蔽了底层串口通信(需要了解Android串口通信的可以看下android-serialport-api ),直接使用UsbDevice进行通信,连接到UsbDevice,读取数据,好处是支持中文扫码。
上代码:
USB HID-KBW,这种模式我们可以自定义EditText对内容进行拦截,过滤出我们想要的内容或者触发我们的业务逻辑。自定义EditText很简单,初始化的时候设置调用setOnKeyListener方法,设置OnKeyListener,通过onKey方法返回true来拦截键盘事件,至于拦截到的键盘事件通过KeyEvent的对应常量转换成相应的字符(注意这里需要让EditText获取到焦点,不然KeyEvent不会触发)。
public class CustomEditText extends android.support.v7.widget.AppCompatEditText {
private ScanCodeListener codeListener;
private StringBuffer buffer = new StringBuffer();
public CustomEditText(Context context) {
super(context);
init();
}
public CustomEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private int preKeyCode = -1;
private void init() {
setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP) {
Log.d("ooo", "Code:" + keyCode + ", display int:" + (int) event.getDisplayLabel() + ", char:" + event.getDisplayLabel());
if (keyCode == KeyEvent.KEYCODE_ENTER) {
String str = buffer.toString();
buffer.setLength(0);
preKeyCode = -1;
if (codeListener != null) {
codeListener.handleCode(str);
}
} else if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
preKeyCode = KeyEvent.KEYCODE_SHIFT_RIGHT;
} else {
if (preKeyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
String shiftChar = SHIFT_KEY_MAP.get(keyCode);
if (shiftChar != null) {
buffer.append(shiftChar);
}
} else {
char label = event.getDisplayLabel();
if (label != 0) {
buffer.append(toLowCase(label));
}
}
preKeyCode = -1;
}
}
return true;
}
});
}
private char toLowCase(char c) {
if (c >= 'A' && c <= 'Z') {
c += 32;
return c;
}
return c;
}
public void setScanCodeListener(ScanCodeListener codeListener) {
this.codeListener = codeListener;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
boolean value = super.dispatchKeyEventPreIme(event);
return value;
}
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
}
@Override
public void setOnKeyListener(OnKeyListener l) {
super.setOnKeyListener(l);
}
public interface ScanCodeListener {
void handleCode(String code);
}
static SparseArray<String> SHIFT_KEY_MAP = new SparseArray<>();
static {
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_GRAVE, "~");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_0, ")");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_1, "!");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_2, "@");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_3, "#");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_4, "$");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_5, "%");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_6, "^");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_7, "&");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_8, "*");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_9, ")");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_MINUS, "_");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_EQUALS, "+");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_LEFT_BRACKET, "{");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_RIGHT_BRACKET, "}");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_BACKSLASH, "|");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_SEMICOLON, ":");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_APOSTROPHE, "\"");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_COMMA, "<");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_PERIOD, ">");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_SLASH, "?");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_A, "A");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_B, "B");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_C, "C");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_D, "D");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_E, "E");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_F, "F");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_G, "G");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_H, "H");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_I, "I");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_J, "J");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_K, "K");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_L, "L");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_M, "M");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_N, "N");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_O, "O");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_P, "P");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_Q, "Q");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_R, "R");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_S, "S");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_T, "T");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_U, "U");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_V, "V");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_W, "W");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_X, "X");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_Y, "Y");
SHIFT_KEY_MAP.put(KeyEvent.KEYCODE_Z, "Z");
}
}
USB 虚拟串口,这种模式稍稍复杂点,要自己去连接USB设备,读取数据,其实也很简单
- 找到设备
- 设备授权
- 连接设备
- 读取数据
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public static final String ACTION_DEVICE_PERMISSION = "com.demo.USB_PERMISSION";
private final int MY_PRODUCT_ID = 33286;//产品ID,根据你的设备修改
private final int MY_VENDOR_ID = 7851; //厂商ID,根据你的设备修改
private UsbManager usbManager;
private TextView textView;
private UsbEndpoint inUsbEndpoint;
private UsbEndpoint outUsbEndpoint;
private UsbInterface mUsbInterface;
private UsbDeviceConnection mUsbDeviceConnection;
private Thread thread;
private Buff buff = new Buff(); //负责分割扫码数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
findViewById(R.id.list_usb_device).setOnClickListener(this);
findViewById(R.id.connect).setOnClickListener(this);
findViewById(R.id.read).setOnClickListener(this);
findViewById(R.id.send).setOnClickListener(this);
findViewById(R.id.clear).setOnClickListener(this);
textView = (TextView) findViewById(R.id.device_info);
}
protected void onResume() {
super.onResume();
IntentFilter usbFilter = new IntentFilter();
usbFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
usbFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
usbFilter.addAction(ACTION_DEVICE_PERMISSION);
registerReceiver(mUsbReceiver, usbFilter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mUsbReceiver);
}
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
Log.d("xxx", "ACTION_USB_DEVICE_ATTACHED");
} else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
Log.d("xxx", "ACTION_USB_DEVICE_DETACHED");
} else if (ACTION_DEVICE_PERMISSION.equals(action)) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
Toast.makeText(MainActivity.this, "已授权回调", Toast.LENGTH_SHORT).show();
if (device != null) {
initCommunication(device);
}
} else {
Toast.makeText(MainActivity.this, "未授权回调", Toast.LENGTH_SHORT).show();
}
}
}
};
@Override
public void onClick(View v) {
if (v.getId() == R.id.list_usb_device) {
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
if (deviceList != null) {
Set<String> keySet = deviceList.keySet();
StringBuffer stringBuffer = new StringBuffer();
for (String key : keySet) {
stringBuffer.append(deviceList.get(key).toString() + "\n");
stringBuffer.append("\n\n");
}
textView.setText(stringBuffer.toString());
} else {
textView.setText("没有设备");
}
} else if (v.getId() == R.id.connect) {
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
if (deviceList.size() > 0) {
for (UsbDevice device : deviceList.values()) {
if (MY_VENDOR_ID == device.getVendorId() && MY_PRODUCT_ID == device.getProductId()) {
if (usbManager.hasPermission(device)) {
initCommunication(device);
continue;
} else {
requestUsbPermission(device);
}
break;
}
}
}
} else if (v.getId() == R.id.read) {
startRead();
} else if (v.getId() == R.id.send) {
sendData();
} else if (v.getId() == R.id.clear) {
textView.setText("");
}
}
//申请授权
private void requestUsbPermission(UsbDevice device) {
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_DEVICE_PERMISSION), 0);
usbManager.requestPermission(device, mPermissionIntent);
}
//开启线程读取数据
private void startRead() {
if (mUsbDeviceConnection == null && !mUsbDeviceConnection.claimInterface(mUsbInterface, true))
return;
thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
byte[] bytes = new byte[inUsbEndpoint.getMaxPacketSize()];
//读取数据, 这里注意最后一个参数为0时 为阻塞读取,知道读到数据为止
int ret = mUsbDeviceConnection.bulkTransfer(inUsbEndpoint, bytes, bytes.length, 0);
if (ret > 0) {
//这里因为扫码器的maxPacketSize很小(我的设备只有64byte),所以扫码到的结果可能分多次读取,
//所以这里将读取到的数据丢到Buff里面,Buff负责分割成单条数据
buff.append(Arrays.copyOf(bytes, ret));
//分割获取单条扫码数据
byte[] code = buff.getCode();
if (code != null) {
final String str = new String(code);
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText(str);
}
});
}
} else {
Log.d("xxx", "Read empty data");
}
}
}
});
thread.start();
}
//初始化设备,找到对应的通信接口
private void initCommunication(UsbDevice device) {
int interfaceCount = device.getInterfaceCount();
for (int interfaceIndex = 0; interfaceIndex < interfaceCount; interfaceIndex++) {
UsbInterface usbInterface = device.getInterface(interfaceIndex);
if ((UsbConstants.USB_CLASS_CDC_DATA != usbInterface.getInterfaceClass())
&& (UsbConstants.USB_CLASS_COMM != usbInterface.getInterfaceClass())
&& UsbConstants.USB_CLASS_HID != usbInterface.getInterfaceClass()) {
continue;
}
for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
UsbEndpoint ep = usbInterface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
inUsbEndpoint = ep;
} else {
outUsbEndpoint = ep;
}
}
}
if (null == inUsbEndpoint) {
inUsbEndpoint = null;
outUsbEndpoint = null;
mUsbInterface = null;
} else {
//连接成功
mUsbInterface = usbInterface;
mUsbDeviceConnection = usbManager.openDevice(device);
initConnection();
break;
}
}
if (mUsbDeviceConnection == null) {
Toast.makeText(this, "连接失败", Toast.LENGTH_SHORT).show();
}
}
//初始化,设置比特率
private void initConnection() {
if (!mUsbDeviceConnection.claimInterface(mUsbInterface, true)) {
return;
}
int ret = mUsbDeviceConnection.controlTransfer(0x21, 0x22, 0x00, 0, null, 0, 0);
setCdcBaudrate(9600);
}
private void setCdcBaudrate(int baudrate) {
byte[] baudByte = new byte[4];
baudByte[0] = (byte) (baudrate & 0x000000FF);
baudByte[1] = (byte) ((baudrate & 0x0000FF00) >> 8);
baudByte[2] = (byte) ((baudrate & 0x00FF0000) >> 16);
baudByte[3] = (byte) ((baudrate & 0xFF000000) >> 24);
int ret = mUsbDeviceConnection.controlTransfer(0x21, 0x20, 0, 0, new byte[]{
baudByte[0], baudByte[1], baudByte[2], baudByte[3], 0x00, 0x00,
0x08
}, 7, 0);
}
//发送数据
private void sendData() {
byte[] buffer = "test data".getBytes();
int ret = mUsbDeviceConnection.controlTransfer(UsbConstants.USB_DIR_OUT, 1, 1, 1, buffer, buffer.length, 1000);
Toast.makeText(this, "send " + ret + " bytes data", Toast.LENGTH_SHORT).show();
}
}
Buff 用于 分割扫码数据,我用的设备是以回车符(0X )+换行符(0X0A)为扫码结束分隔符
public class Buff {
private final int DELIMITER_1 = 0xd; //回车符
private final int DELIMITER_2 = 0xa; //换行符
private byte[] buffer;
public void append(byte[] data) {
if (buffer == null) {
buffer = new byte[data.length];
System.arraycopy(data, 0, buffer, 0, data.length);
} else {
byte[] temp = new byte[buffer.length + data.length];
System.arraycopy(buffer, 0, temp, 0, buffer.length);
System.arraycopy(data, 0, temp, buffer.length, data.length);
buffer = temp;
}
}
public byte[] getCode() {
if (buffer == null)
return null;
if (buffer.length > 2) {
boolean found = false;
int i = 0;
for (; i < buffer.length - 1; i++) {
if (buffer[i] == DELIMITER_1 && buffer[i + 1] == DELIMITER_2) {
found = true;
break;
}
}
if (found) {
byte[] result = Arrays.copyOf(buffer, i);
if (i < buffer.length - 2) {
buffer = Arrays.copyOfRange(buffer, i + 2, buffer.length);
} else {
buffer = null;
}
return result;
}
}
return null;
}
}