Android电子牌外接USB读卡器读取内容模拟键盘事件
16lz
2021-01-26
最近做了一个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文件中记得配置
res目录下创建xml文件夹,并在xml文件夹内创建accessibility.xml文件内容如下
<?xml version="1.0" encoding="utf-8"?>accessibility_description在res的values文件夹内strings中设置你想要说明的,例如xxx按键监听的无障碍辅助服务
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(); }
更多相关文章
- Android:自定义输入法(输入密码时防止第三方窃取)
- Android客户端请求服务端资源(HttpURLConnection和输入流实现)
- android:imeOptions属性详解以及无效处理
- android 中用代码模拟发送按键
- MediaButtonReceiver---独特的媒体广播接收器
- Android(安卓)EditText 限制只能输入指定范围的数字
- 实现Android监控任意控件或按键双击事件方法
- macOS中配置Flutter开发环境(使用AndroidStudio开发)傻瓜版教程
- Android搜索框输入内容点击键盘的搜索按钮进行搜索