Android电子牌外接USB读卡器读取内容模拟键盘事件

最近做了一个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文件内容如下




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)