巧用事件分發機制,和我一塊兒hold住android外圍設備

外圍輸入設備,例如:藍牙鍵盤,usb鍵盤,barcode掃碼槍...
因爲平時都是在作純軟件程序的開發,博主在需求遇到android設備與外圍設備交互時有點不知所措。我最初的思路是這樣:既然是藍牙鏈接,那不就是socket嗎,那麼截獲他的I/O流而後解析裏面的內容...那不就ok啦?
然而事情並無那麼簡單,首先解析數據流是一個難點,再一個萬一我藍牙鏈接換成usb鏈接,或者wifi,那不就得再改了?
參考了網上的方案後發現,外圍設備是經過KeyEvent事件機制android交互的,既然是這樣那我就不用再關心外設是經過什麼方式鏈接的,直接截獲外設發送的事件,而且還可直接獲取到輸入內容!android

本文將經過一個android設備與掃碼槍鏈接案例來向你們詳述android的事件機制

首先看一個我具體使用的類庫

如下就是我寫的的藍牙掃碼槍的類庫,已上傳至github
https://github.com/sally519/BarCode-androidgit

android外圍設備.png
咱們直接來看看是怎麼用的github

public class MainActivity extends AppCompatActivity implements BarCodeIpml.OnScanSuccessListener {

//activity實現了BarCodeIpml.OnScanSuccessListener藉口,即回調成功時的藉口

private BarCodeIpml barCodeIpml = new BarCodeIpml();
private TextView textView;
private TextView mTv;
private static final String TAG="MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //設置掃碼成功的回調監聽
    barCodeIpml.setOnGunKeyPressListener(this);
    mTv = (TextView) findViewById(R.id.mTv);
}

//重寫事件分發的方法
@Override
public boolean dispatchKeyEvent(KeyEvent event) {

    if (barCodeIpml.isEventFromBarCode(event)) {
        barCodeIpml.analysisKeyEvent(event);
        return true;
    }
    Log.e("keycode",event.getKeyCode()+"");
    return super.dispatchKeyEvent(event);
}
//在activity得到焦點時
@Override
protected void onResume() {
    super.onResume();
    try {
        barCodeIpml.hasConnectBarcode();
    } catch (DevicePairedNotFoundException e) {
        e.printStackTrace();
        Log.e(TAG, "badcode槍未鏈接!");
    }
}
 //藉口的實現,掃碼成功後的回調,寫你本身的實現
@Override
public void onScanSuccess(String barcode) {
    Log.e("mcallback", barcode);
    mTv.setText(barcode);
}

 //與activity生命週期綁定,防止內存泄漏
@Override
protected void onDestroy() {
    super.onDestroy();
    barCodeIpml.onComplete();
}
}

從註釋和方法的名稱咱們大概能夠判斷出各個方法的做用,咱們來看其中最關鍵的一個方法 socket

dispatchKeyEvent(KeyEvent event)

你們首先須要知道,這個方法返回一個boolean ide

@Override
public boolean dispatchKeyEvent(KeyEvent event) {

    if (barCodeIpml.isEventFromBarCode(event)) {
        barCodeIpml.analysisKeyEvent(event);
        return true;
    }
    Log.e("keycode",event.getKeyCode()+"");
    return super.dispatchKeyEvent(event);
}

當我返回一個true的時候,這個事件就會被咱們消費(類庫裏的主要操做就是獲取KeyCode,即外圍設備輸入的內容),不會再交給系統處理。在以上的代碼中我拿到這個事件後我調用類庫的方法判斷他是否來自外圍設備,若是是的話咱們本身將其截獲處理,再也不交給系統,不然的話咱們交由系統處理。這個方法你們應該比較熟悉,咱們經常用來重寫back鍵或者home鍵。
以後咱們進去dispatchKeyEvent裏面看一下,系統是如何處理這個事件的,直接在activity中查看源碼this

/**
 * Called to process key events.  You can override this to intercept all
 * key events before they are dispatched to the window.  Be sure to call
 * this implementation for key events that should be handled normally.
 *
 * @param event The key event.
 *
 * @return boolean Return true if this event was consumed.
 */
public boolean dispatchKeyEvent(KeyEvent event) {
    onUserInteraction();

    // Let action bars open menus in response to the menu key prioritized over
    // the window handling it
    final int keyCode = event.getKeyCode();
    if (keyCode == KeyEvent.KEYCODE_MENU &&
            mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
        return true;
    } else if (event.isCtrlPressed() &&
            event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK) == '<') {
        // Capture the Control-< and send focus to the ActionBar
        final int action = event.getAction();
        if (action == KeyEvent.ACTION_DOWN) {
            final ActionBar actionBar = getActionBar();
            if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) {
                mEatKeyUpEvent = true;
                return true;
            }
        } else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) {
            mEatKeyUpEvent = false;
            return true;
        }
    }

源碼不算複雜,註釋說你能夠在事件發送到窗口前攔截此事件,但要對關鍵事件執行,這裏說的關鍵事件多是back鍵或者home鍵。
咱們一步一步來作下分析,先來看看onUserInteraction()spa

/**
 * Called whenever a key, touch, or trackball event is dispatched to the
 * activity.  Implement this method if you wish to know that the user has
 * interacted with the device in some way while your activity is running.
 * This callback and {@link #onUserLeaveHint} are intended to help
 * activities manage status bar notifications intelligently; specifically,
 * for helping activities determine the proper time to cancel a notfication.
 *
 * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
 * be accompanied by calls to {@link #onUserInteraction}.  This
 * ensures that your activity will be told of relevant user activity such
 * as pulling down the notification pane and touching an item there.
 *
 * <p>Note that this callback will be invoked for the touch down action
 * that begins a touch gesture, but may not be invoked for the touch-moved
 * and touch-up actions that follow.
 *
 * @see #onUserLeaveHint()
 */
 public void onUserInteraction() {
}

實現竟然是空的,註釋上說當事件分發到activity的時候調用,你能夠實現這個方法來獲知用戶是否在和當前activity交互。onUserInteraction()和onUserLeaveHint()是本意是幫助activity更好的管理推送消息,後者將會跟隨前者一塊兒調用,上面還說onUserInteraction()可能不會跟隨着手指的移動而調用。
由此,咱們也知道onUserInteraction()會在事件分發最初的時候調用,咱們能夠用這和方法監聽用戶於activity的交互。
咱們接着往下看code

// Let action bars open menus in response to the menu key prioritized over
// the window handling it
final int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_MENU &&
        mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
    return true;
}

首先獲取用戶的輸入內容keyCode ,以後的意圖很明顯,若是用戶點擊的是ToolBar或者ActionBar的菜單那直接return了一個true,咱們剛剛說過return一個true的意思就是事件再也不交給系統處理,return一個false則依舊須要交給系統處理,這裏的目的想必就是把咱們的事件拋給ToolBar或者ActionBar來處理。
接着往下看orm

else if (event.isCtrlPressed() &&
            event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK) == '<') {
        // Capture the Control-< and send focus to the ActionBar
        final int action = event.getAction();
        if (action == KeyEvent.ACTION_DOWN) {
            final ActionBar actionBar = getActionBar();
            if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) {
                mEatKeyUpEvent = true;
                return true;
            }
        } else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) {
        mEatKeyUpEvent = false;
        return true;
    }

第一個判斷條件event.isCtrlPressed() &&event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK) == '<'或許有點抽象,但下面的註釋告訴咱們這段代碼的意圖是捕獲「<」鍵,以後判斷ActionBar是否在請求焦點,若是是的話強行將ACTION_UP的事件(也就是按壓屏幕後擡起那一下)消費,再也不由系統處理,而且強行讓ActionBar得到焦點,交由ActionBar處理。不然告訴activityACTION_UP的事件還未被消費。生命週期

搞懂了以上的內容咱們就能夠隨意截獲事件,並經過KeyCode獲知是否點擊了虛擬鍵盤,具體點擊的是哪個鍵?若是是回車鍵,那就將其內容經過回調發送給activity,由此來獲取外圍輸入設備的內容。具體可查看 https://github.com/sally519/BarCode-android 中的使用詳情。
#小結
好了,咱們來總結幾個須要特別注意的點

  • dispatchKeyEvent(KeyEvent event)中,但咱們返回了true,則事件將會被消費,不會在有系統處理,當返回false則還要交由系統來處理
  • onUserInteraction()和onUserLeaveHint()能夠用來監聽用戶的和activity的交互,注意:activity必須是得到焦點的
  • 咱們在消費事件的時候須要對注意home鍵back鍵等經常使用的鍵,若是沒有特殊需求,記得將他們拋給系統處理
博主不免有疏忽和遺漏,如有錯誤或疑點歡迎指正!
以上內容均爲原創,歡迎關注博主!
相關文章
相關標籤/搜索