最近做了一个Android外接USB读卡器刷手环读取数据,模拟键盘输入事件的项目;
借鉴了https://github.com/githubRonda/BarcodeScanner
连接电子牌板子调试,可以将板子上OTG跳帽取下,然后用一根双USB口的线连接电脑就可以调试了(ps:板子上一般连接靠近网线的USB口)
因为之前公司的Android系统的电子牌的读卡器是通过串口开发的,最近由于换了电子牌的厂商,手环读取方式也更改了,无奈研究了一番,从网上找了相关文章,但是没有找到具体的,其中根据某个大神的外接扫码器的项目,结合实际终于实现了;
不管外接扫码枪还是外接读卡器,其实原理就我了解的原理是一样的,都是会将扫描到或者刷卡读取到的数据模拟成键盘输入的事件,你会发现,当Android界面有一个焦点输入框时候,扫码或者刷卡,数据都会自动填充输入框, 那问题也随之而来,我们需要做的其实就是监听按键输入事件,然后获取扫到或者读取到的数据,之后进行一些其他方面的操作.
回归正文:
后台无障碍服务AccessibilityService配置
import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
/**
* Created by Administrator on 2019/4/22.
*/
public class ReadCardService extends AccessibilityService {
private static final String TAG = ReadCardService.class.getSimpleName();
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.e(TAG, "onAccessibilityEvent --> " + event);
}
@Override
public void onInterrupt() {
Log.e(TAG, "onInterrupt");
}
/**
* 复写这个方法可以捕获按键事件
*
* @param event
* @return
*/
@Override
protected boolean onKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
Log.e(TAG, "keyEvent:" + event + "keyCode: " + keyCode + "char: " + KeyEvent.keyCodeToString(keyCode));
return super.onKeyEvent(event);
}
}
在AndroidManifest.xml文件中记得配置
<service
android:name=".readService.ReadCardService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility"/>
</service>
res目录下创建xml文件夹,并在xml文件夹内创建accessibility.xml文件内容如下
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagRequestFilterKeyEvents"
android:canRetrieveWindowContent="true"
android:canRequestFilterKeyEvents="true"
android:description="@string/accessibility_description"
android:packageNames="com.xxx.xxxdemo你的项目包名"/>
accessibility_description在res的values文件夹内strings中设置你想要说明的,例如
<string name="accessibility_description">xxx按键监听的无障碍辅助服务</string>
ReadCardUtils工具类
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
/**
* Created by Administrator on 2019/4/22.
*
* 使用说明:
* * 1. 在Activity中先创建ReadCardUtils对象,并设置扫码成功监听器: setReadSuccessListener() [一般在onCreate()方法中初始化]
* * 2. 接着在Activity#dispatchKeyEvent() 或者 Activity#onKeyDown() 中调用本类中的resolveKeyEvent()方法。当扫码结束之后,会自动回调第一步设置的监听器中的方法
* *
* * 原理分析:
* * 1. 扫码枪就是一个外部的输入设备(和键盘一样)。扫码的时候,就是在极短的时间内输入了一系列的数字或字母
* * 2. 这样就可以在键盘事件中抓捕这些输入的字符,但是又会产生一个问题(快速扫两次的情形):在键盘事件中应该抓捕多少个字符呢?即一个条码应该在哪个位置结束呢? (有的扫码枪会以一个回车或者换行作为一次扫码的结束符,但是有的就纯粹的是一系列的条码。这个得需要设置)
* * 所以为了兼容性,应当是当短时间内不再输入字符的时候,就表示扫码已结束。这样只能定性描述,不能定量,只能自己在程序中用一个具体的数字来表示这个“短时间”,eg:500ms。(如果每个条码结束的时候都有一个结束符那该多好,直接判断这个结束符,就可以知道当前扫码已完成)
* *
* * 接下来就产生了ReadCardUtils这个类。
* * 核心原理就一句话:在Activity的键盘监听事件中,每抓捕到一个字符的时候,就先向 Handler 一次一个runnable对象,再延迟500ms发送一个runnable. 这样若两个输入字符的间隔时间超过了500ms,则视为两次扫码
*
*/
public class ReadCardUtils {
private static final String TAG = ReadCardUtils.class.getSimpleName();
// 若500ms之内无字符输入,则表示扫码完成. (若觉得时间还长,则可以设置成更小的值)
private final static long MESSAGE_DELAY = 500;
private boolean mCaps;//大写或小写
private StringBuilder mResult = new StringBuilder();//扫码内容
private OnReadSuccessListener mOnReadSuccessListener;
private Handler mHandler = new Handler();
private final Runnable mReadingEndRunnable = new Runnable() {
@Override
public void run() {
performScanSuccess();
}
};
//调用回调方法
private void performScanSuccess() {
String barcode = mResult.toString();
//Log.i(TAG, "performScanSuccess -> barcode: "+barcode);
if (mOnReadSuccessListener != null) {
mOnReadSuccessListener.onScanSuccess(barcode);
}
mResult.setLength(0);
}
//key事件处理
public void resolveKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
checkLetterStatus(event);//字母大小写判断
Log.w(TAG, "keyEvent:" + event + "keyCode: " + keyCode + "char: " + KeyEvent.keyCodeToString(keyCode));
if (event.getAction() == KeyEvent.ACTION_DOWN) {
char aChar = getInputCode(event);
Log.w(TAG, "aChar: " + aChar);
if (aChar != 0) {
mResult.append(aChar);
}
if (keyCode == KeyEvent.KEYCODE_ENTER) {
//若为回车键,直接返回
mHandler.removeCallbacks(mReadingEndRunnable);
mHandler.post(mReadingEndRunnable);
} else {
//延迟post,若500ms内,有其他事件
mHandler.removeCallbacks(mReadingEndRunnable);
mHandler.postDelayed(mReadingEndRunnable, MESSAGE_DELAY);
}
}
}
//检查shift键
private void checkLetterStatus(KeyEvent event) {
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT || keyCode == KeyEvent.KEYCODE_SHIFT_LEFT) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
//按着shift键,表示大写
mCaps = true;
} else {
//松开shift键,表示小写
mCaps = false;
}
}
}
//获取扫描内容
private char getInputCode(KeyEvent event) {
int keyCode = event.getKeyCode();
char aChar;
if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
//字母
aChar = (char) ((mCaps ? 'A' : 'a') + keyCode - KeyEvent.KEYCODE_A);
} else if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
//数字
aChar = (char) ('0' + keyCode - KeyEvent.KEYCODE_0);
} else {
//其他符号
switch (keyCode) {
case KeyEvent.KEYCODE_PERIOD:
aChar = '.';
break;
case KeyEvent.KEYCODE_MINUS:
aChar = mCaps ? '_' : '-';
break;
case KeyEvent.KEYCODE_SLASH:
aChar = '/';
break;
case KeyEvent.KEYCODE_BACKSLASH:
aChar = mCaps ? '|' : '\\';
break;
default:
aChar = 0;
break;
}
}
return aChar;
}
/**
* 检测输入设备是否是读卡器
*
* @param context
* @return 是的话返回true,否则返回false
*/
public static boolean isInputFromReader(Context context, KeyEvent event) {
if (event.getDevice() == null) {
return false;
}
// event.getDevice().getControllerNumber();
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK || event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN || event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP) {
//实体按键,若按键为返回、音量加减、返回false
return false;
}
if (event.getDevice().getSources() == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD | InputDevice.SOURCE_CLASS_BUTTON)) {
//虚拟按键返回false
return false;
}
Configuration cfg = context.getResources().getConfiguration();
return cfg.keyboard != Configuration.KEYBOARD_UNDEFINED;
}
public interface OnReadSuccessListener {
void onScanSuccess(String barcode);
}
public void setReadSuccessListener(OnReadSuccessListener onReadSuccessListener) {
mOnReadSuccessListener = onReadSuccessListener;
}
public void removeScanSuccessListener() {
mHandler.removeCallbacks(mReadingEndRunnable);
mOnReadSuccessListener = null;
}
}
在你的MainActivity类中
声明读取工具类ReadCardUtils服务,并在合适的位置初始化
//U口读卡器,类似于外接键盘
private ReadCardUtils readCardUtils;
初始化
//读卡器声明
readCardUtils = new ReadCardUtils();
initCardReader();
/**
* 读卡器初始化
*/
private void initCardReader() {
readCardUtils.setReadSuccessListener(new ReadCardUtils.OnReadSuccessListener() {
@Override
public void onScanSuccess(String barcode) {
Log.e(TAG, "barcode: " + barcode);
}
});
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (ReadCardUtils.isInputFromReader(this, event)) {
if (readCardUtils != null){
readCardUtils.resolveKeyEvent(event);
}
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return super.onKeyDown(keyCode, event);
}
@Override
protected void onDestroy() {
readCardUtils.removeScanSuccessListener();
readCardUtils = null;
super.onDestroy();
}
安卓USB免驱读取卡号数据demo (demo无添加界面提示,需要在控制面板查看log输出)源码demo下载链接
---------------------
来源:CSDN
原文:https://blog.csdn.net/joyu_/article/details/89641271
版权声明:转载请附上博文链接!