Android 獲取USB掃描槍簡易封裝

最近作了個關於Android設備Usb外接掃碼器的項目,在此記錄下。掃碼器有如下這兩種模式:
  • USB HID-KBW:掃碼器會將掃描出來的內容轉化爲鍵盤事件,就是Android中就是KeyEvent裏面對應的常量(0 = KeyEvent.KEYCODE_0)。
  • USB 虛擬串口:可以使用android-serialport-api 鏈接到UsbDevice進行通訊,讀取數據。(設備要支持串口)

支持 Android 熱插拔USB掃描槍會在有EditText時,掃描槍掃描內容自動輸入到編輯框了,在沒有EditText的狀況下呢?還會響應獲焦控件的點擊事件(如Button),由於標準掃描槍掃描數據會觸發KEYCODE_ENTER鍵。android

經過USB 虛擬串口方式,這個我喜歡,但是它不支持! 項目需求:git

  • 掃碼槍掃商品條形碼時返回內容(一般一串數字),做爲購買時惟一標識

掃碼槍是基於鍵盤輸入的,那事件會先分發到獲取焦點的Activity、Dialog 中的,dispatchKeyEvent(KeyEvent event) .因此很好解決了由安卓事件分發機制看,只要消費了掃碼器產生的事件,就不須要EditText,也不會觸發到其它組件了。那好,如今新的問題又來了,dispatchKeyEvent(KeyEvent event) 是按鍵事件分發的第一個要塞,並且沒辦法統一爲應用設置監聽,只能在每一個Activity、Dialog做監聽。這裏能夠基類(BaseActivity)處理掃碼器的輸入事件,也能夠經過AccessibilityService 的 onKeyEvent(KeyEvent event) 事件去處理,但無障礙輔助須要手動開啓,不太友好。
查看 KeyEvent 源碼一看繼承 InputEvent,正好能夠經過 InputDevice getDevice() 獲取輸入設備,根據輸入設備正好判斷該事件輸入掃碼槍輸入github

如下是BarCodeHelper.kt 處理掃碼器輸入事件且回調條形碼numberapi

//掃碼設備名稱
const val BARCODE_DEVICES = "Barcode Reader"
var scannerResult = StringBuilder()

fun hasBarcodeInputDeviceExist(): Boolean {
    InputDevice.getDeviceIds().forEach {
        val name = InputDevice.getDevice(it).name.trim()
        Log.i("InputDevice", name)
        if (BARCODE_DEVICES == name) {
            return true
        }
    }
    return false
}

fun KeyEvent.isBarcodeKeyEvent() = this.device.name.trim() == BARCODE_DEVICES

fun Activity.transformBarCodeKeyEvent(event: KeyEvent, listener: (result: String) -> Unit): Boolean {
    if (event.action == KeyEvent.ACTION_DOWN && event.repeatCount == 0) {
        val keyCode = event.keyCode
        if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
            scannerResult.append(keyCode - KeyEvent.KEYCODE_0)
            return true
        }
        if (keyCode == KeyEvent.KEYCODE_ENTER) {
            listener.invoke(scannerResult.toString())
            scannerResult = StringBuilder()
            return true
        }
    }
    return false
}

fun Dialog.transformBarCodeKeyEvent(event: KeyEvent, listener: (result: String) -> Unit): Boolean {
    if (event.action == KeyEvent.ACTION_DOWN && event.repeatCount == 0) {
        val keyCode = event.keyCode
        if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
            scannerResult.append(keyCode - KeyEvent.KEYCODE_0)
            return true
        }
        if (keyCode == KeyEvent.KEYCODE_ENTER) {
            listener.invoke(scannerResult.toString())
            scannerResult = StringBuilder()
            return true
        }
    }
    return false
}

BaseActivityapp

override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        //多數activity 不須要掃碼輸入,只要是掃碼設備事件都消費掉,以防止觸碰到控件
        return if (event.isBarcodeKeyEvent()) {
            this.transformBarCodeKeyEvent(event) { barCode ->
                Log.i("Barcode", "barCode: " + barCode)
                //EventBus
                EventBus.getDefault().post(barCode, PRODUCT_BAR_CODE_EVENT)
            }
        } else super.dispatchKeyEvent(event)
    }

另外你要是採起AccessibilityService 方式的話,又經過如下方式去設置的話,onKeyEvent(KeyEvent event)不回調,只能經過xml 方式註冊ide

@Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        Log.v(TAG, "on Service Connected");
        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
        info.packageNames = null;
        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
        info.notificationTimeout = 0;
        info.feedbackType = AccessibilityEvent.TYPES_ALL_MASK;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            info.flags = AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS;
        }
        setServiceInfo(info);

        System.out.println(getServiceInfo());
    }

這裏meta-data 只能寫在 service 節點下post

<meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/serviceconfig" />
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagRequestFilterKeyEvents"
    android:canRequestFilterKeyEvents="true"
    android:canRetrieveWindowContent="true"
    android:description="@string/app_name"
    android:notificationTimeout="100"
    android:packageNames="" />
相關文章
相關標籤/搜索