Android使用AccessibilityService实现USB扫码枪数据抓取

android单屏机,通过扫码枪扫描二维码的场景非常多,扫码枪的种类也有蓝牙、USB、串口等等


目前USB的扫码枪主流的就是以下两种

1、USB HID-KBW:扫码器会将扫描出来的内容转化为键盘事件,就是Android中KeyEvent里面对应的常量(KeyEvent.KEYCODE_*)。

2、USB 虚拟串口:可使用android-serialport-api 连接到UsbDevice进行通信,读取数据。(设备要支持串口)


支持 Android 热插拔USB扫描枪会在有EditText时,扫描枪扫描内容自动输入到编辑框了,但是有很多输入法兼容的问题,比如搜狗输入法识别到HID设备时会隐藏无法弹出,如果输入法切换成中文时会输入中文等等。

通过串口的方式直接获取原始数据,不再跟输入法产生冲突,可惜设备是USB HID的,通过大量的尝试(包括USB虚拟串口)都不支持(对串口不了解的同学可以先看看这篇文章上半年最好的Android串口开发入门指南 - 简书

扫码枪是基于键盘输入的,尝试从获取焦点的Activity中的dispatchKeyEvent(KeyEvent event)进行拦截,可惜只能解决掉中文的问题,事件还是先走到输入法才能回到Activity。于是强大的AccessibilityService就上场了,使用AccessibilityService可以优先获取到键盘事件。

使用强大的AccessibilityService(Google为了让Android系统更实用,为用户提供了无障碍辅助服务),但需要到系统设置->无障碍->服务 开启当前服务。对AccessibilityService不了解的同学看看http://www.jianshu.com/p/4cd8c109cdfb

废话不多说看实现步骤

1、先创建扫码Service直接继承AccessibilityService就OK

public class ScanService extends AccessibilityService {
    private static OnKeyEvent onKeyEvent;
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
    }

    @Override
    public void onInterrupt() {
    }

    @Override
    protected boolean onKeyEvent(KeyEvent event) {
        if(onKeyEvent!=null){
          //这里通过回调的方式将事件传出去统一处理
          //返回true事件就会拦截不会继续传递
           return onKeyEvent.onKeyEvent(event);
        }
        return super.onKeyEvent(event);
    }
    /**
     * 设置监听
     * @param onKeyEvent
     */
    public static void setOnKeyEvent(OnKeyEvent onKeyEvent){
        ScanService.onKeyEvent=onKeyEvent;
    }
    public interface OnKeyEvent{
        boolean onKeyEvent(KeyEvent event);
    }
}

2、创建好自己的ScanService后需要在manifest中进行注册

 <service
            android:name="包名.service.ScanService"
            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>

创建android:resource需要用到的xml ,在res下新建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:notificationTimeout="100"
    android:canRequestFilterKeyEvents="true"
    android:description="@string/accessibility_description"
    android:packageNames="包名" />

android:description指定一个String作为描述文案

<string name="accessibility_description">这里是描述辅助功能的文案</string>

到此为止AccessibilityService就配置好了,你的应用就会出现在系统设置->辅助功能列表里,只需要手动在设置中打开辅助功能,扫码枪的键盘事件就会触发ScanService的onKeyEvent

接下来是对事件的处理
1、过滤非扫码枪的设备

  /**
     * 检测输入设备是否是扫码器
     *
     * @param context
     * @return 是的话返回true,否则返回false
     */
    public boolean isInputFromScanner(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;
    }

2、处理事件

Runnable mScanningFishedRunnable = new Runnable() {
            @Override
            public void run() {
                String code = mStringBufferResult.toString();
                //做相应处理....
                mStringBufferResult.setLength(0);
            }
        };
/**
     * 扫码枪事件解析
     *
     * @param event
     */
    public void analysisKeyEvent(KeyEvent event) {

        int keyCode = event.getKeyCode();

        //字母大小写判断
        checkLetterStatus(event);

        if (event.getAction() == KeyEvent.ACTION_DOWN) {

            char aChar = getInputCode(event);
//            char aChar = (char) event.getUnicodeChar();

            if (aChar != 0) {
                mStringBufferResult.append(aChar);
            }

            if (keyCode == KeyEvent.KEYCODE_ENTER) {
                //若为回车键,直接返回
                mHandler.removeCallbacks(mScanningFishedRunnable);
                mHandler.post(mScanningFishedRunnable);
            } else {
                //延迟post,若500ms内,有其他事件
                mHandler.removeCallbacks(mScanningFishedRunnable);
                mHandler.postDelayed(mScanningFishedRunnable, 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 if (keyCode == KeyEvent.KEYCODE_ENTER) {
            aChar = 0;
        } else {
            //其他符号
            aChar = (char) event.getUnicodeChar();
        }

        return aChar;

    }

扫描完成,获取扫描的数据后,自己想怎么处理就怎么处理

最后附上一些工具类
跳转到系统辅助功能页

  /**
     * 打开设置-辅助功能页
     * @param context
     */
    public void openAccessibilitySetting(Context context){
        context.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
    }

判断当前应用的辅助功能在设置中是否打开

/**
     *
     * @param context
     * @return true辅助功能开 false辅助功能关
     */
    public boolean isAccessibilitySettingsOn(Context context) {
        int accessibilityEnabled = 0;
        final String service = context.getPackageName() + "/" + ScanService.class.getCanonicalName();
        try {
            //获取setting里辅助功能的开启状态
            accessibilityEnabled = Settings.Secure.getInt(
                    context.getApplicationContext().getContentResolver(),
                    android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {
        }
        TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
        if (accessibilityEnabled == 1) {
            //获取辅助功能里所有开启的服务 包名列表
            String settingValue = Settings.Secure.getString(
                    context.getApplicationContext().getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (settingValue != null) {
                //转换程集合
                mStringColonSplitter.setString(settingValue);
                while (mStringColonSplitter.hasNext()) {
                    String accessibilityService = mStringColonSplitter.next();
                    //判断当前包名是否在服务集合里
                    if (accessibilityService.equalsIgnoreCase(service)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

完成。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 最近做了一个关于Android设备Usb外接扫码器的项目,在此记录下。言归正传,首先扫码器有这两种模式:USB H...
    葫芦娃大战屎壳郎阅读 5,193评论 9 12
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    wgl0419阅读 6,121评论 1 9
  • 2018年3月20号的功课: 图片发自简书App 本周的主题:1,专注,活在当下,人生不在于做多少事,在于做了多少...
    再造堂主冯延红阅读 281评论 0 0
  • 今天想抛开一切,写些碎碎念。天南地北的聊着,任思绪纷飞…… 1.不能跑步的日子 上上周跑长距离的时候,为躲过迎面来...
    淺心阅读 317评论 2 3