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

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

本文將通過一個android設備與掃碼槍連接案例來向大家詳述android的事件機制

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

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

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

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();
}
}

從註釋和方法的名稱我們大概可以判斷出各個方法的作用,我們來看其中最關鍵的一個方法

dispatchKeyEvent(KeyEvent event)

大家首先需要知道,這個方法返回一個boolean

 @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中查看源碼

/**
 * 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()

  /**
 * 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的交互。
我們接着往下看

// 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來處理。
接着往下看

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鍵等常用的鍵,如果沒有特殊需求,記得將他們拋給系統處理
博主難免有疏忽和遺漏,若有錯誤或疑點歡迎指正!
以上內容均爲原創,歡迎關注博主!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章