巧用事件分發(fā)機制忧勿,和我一起hold住android外圍設備

外圍輸入設備,例如:藍牙鍵盤瞻讽,usb鍵盤鸳吸,barcode掃碼槍...
由于平時都是在做純軟件程序的開發(fā),博主在需求遇到android設備與外圍設備交互時有點不知所措速勇。我最初的思路是這樣:既然是藍牙連接晌砾,那不就是socket嗎,那么截獲他的I/O流然后解析里面的內(nèi)容...那不就ok啦烦磁?
然而事情并沒有那么簡單养匈,首先解析數(shù)據(jù)流是一個難點哼勇,再一個萬一我藍牙連接換成usb連接,或者wifi呕乎,那不就得再改了积担?
參考了網(wǎng)上的方案后發(fā)現(xiàn),外圍設備是通過KeyEvent事件機制android交互的猬仁,既然是這樣那我就不用再關心外設是通過什么方式連接的帝璧,直接截獲外設發(fā)送的事件,并且還可直接獲取到輸入內(nèi)容!

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

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

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

android外圍設備.png

我們直接來看看是怎么用的

public class MainActivity extends AppCompatActivity implements BarCodeIpml.OnScanSuccessListener {

//activity實現(xiàn)了BarCodeIpml.OnScanSuccessListener借口的烁,即回調(diào)成功時的借口

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

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

//重寫事件分發(fā)的方法
@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槍未連接!");
    }
}
 //借口的實現(xiàn)诈闺,掃碼成功后的回調(diào)渴庆,寫你自己的實現(xiàn)
@Override
public void onScanSuccess(String barcode) {
    Log.e("mcallback", barcode);
    mTv.setText(barcode);
}

 //與activity生命周期綁定,防止內(nèi)存泄漏
@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漓穿,即外圍設備輸入的內(nèi)容)嗤军,不會再交給系統(tǒng)處理。在以上的代碼中我拿到這個事件后我調(diào)用類庫的方法判斷他是否來自外圍設備晃危,如果是的話我們自己將其截獲處理叙赚,不再交給系統(tǒng),否則的話我們交由系統(tǒng)處理僚饭。這個方法大家應該比較熟悉震叮,我們常常用來重寫back鍵或者home鍵。
之后我們進去dispatchKeyEvent里面看一下鳍鸵,系統(tǒng)是如何處理這個事件的苇瓣,直接在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;
        }
    }  

源碼不算復雜,注釋說你可以在事件發(fā)送到窗口前攔截此事件偿乖,但要對關鍵事件執(zhí)行击罪,這里說的關鍵事件可能是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() {
}

實現(xiàn)居然是空的媳禁,注釋上說當事件分發(fā)到activity的時候調(diào)用,你可以實現(xiàn)這個方法來獲知用戶是否在和當前activity交互画切。onUserInteraction()和onUserLeaveHint()是本意是幫助activity更好的管理推送消息竣稽,后者將會跟隨前者一起調(diào)用,上面還說onUserInteraction()可能不會跟隨著手指的移動而調(diào)用。
由此毫别,我們也知道onUserInteraction()會在事件分發(fā)最初的時候調(diào)用娃弓,我們可以用這和方法監(jiān)聽用戶于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;
}

首先獲取用戶的輸入內(nèi)容keyCode 拧烦,之后的意圖很明顯忘闻,如果用戶點擊的是ToolBar或者ActionBar的菜單那直接return了一個true,我們剛剛說過return一個true的意思就是事件不再交給系統(tǒng)處理恋博,return一個false則依舊需要交給系統(tǒng)處理齐佳,這里的目的想必就是把我們的事件拋給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的事件(也就是按壓屏幕后抬起那一下)消費疫衩,不再由系統(tǒng)處理硅蹦,并且強行讓ActionBar獲得焦點,交由ActionBar處理闷煤。否則告訴activityACTION_UP的事件還未被消費童芹。

搞懂了以上的內(nèi)容我們就可以隨意截獲事件,并通過KeyCode獲知是否點擊了虛擬鍵盤鲤拿,具體點擊的是哪一個鍵假褪?如果是回車鍵,那就將其內(nèi)容通過回調(diào)發(fā)送給activity近顷,由此來獲取外圍輸入設備的內(nèi)容生音。具體可查看 https://github.com/sally519/BarCode-android 中的使用詳情。

小結

好了窒升,我們來總結幾個需要特別注意的點

  • dispatchKeyEvent(KeyEvent event)中缀遍,但我們返回了true,則事件將會被消費饱须,不會在有系統(tǒng)處理域醇,當返回false則還要交由系統(tǒng)來處理
  • onUserInteraction()和onUserLeaveHint()可以用來監(jiān)聽用戶的和activity的交互,注意:activity必須是獲得焦點的
  • 我們在消費事件的時候需要對注意home鍵back鍵等常用的鍵蓉媳,如果沒有特殊需求譬挚,記得將他們拋給系統(tǒng)處理
博主難免有疏忽和遺漏,若有錯誤或疑點歡迎指正督怜!
以上內(nèi)容均為原創(chuàng),歡迎關注博主!
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狠角,一起剝皮案震驚了整個濱河市号杠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖姨蟋,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屉凯,死亡現(xiàn)場離奇詭異,居然都是意外死亡眼溶,警方通過查閱死者的電腦和手機悠砚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堂飞,“玉大人灌旧,你說我怎么就攤上這事〈律福” “怎么了枢泰?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铝噩。 經(jīng)常有香客問我衡蚂,道長,這世上最難降的妖魔是什么骏庸? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任毛甲,我火速辦了婚禮,結果婚禮上具被,老公的妹妹穿的比我還像新娘玻募。我一直安慰自己,他們只是感情好硬猫,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布补箍。 她就那樣靜靜地躺著,像睡著了一般啸蜜。 火紅的嫁衣襯著肌膚如雪坑雅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天衬横,我揣著相機與錄音裹粤,去河邊找鬼。 笑死蜂林,一個胖子當著我的面吹牛遥诉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播噪叙,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼矮锈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了睁蕾?” 一聲冷哼從身側(cè)響起苞笨,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤债朵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瀑凝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體序芦,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年粤咪,在試婚紗的時候發(fā)現(xiàn)自己被綠了谚中。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡寥枝,死狀恐怖宪塔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脉顿,我是刑警寧澤蝌麸,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站艾疟,受9級特大地震影響来吩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔽莱,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一弟疆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盗冷,春花似錦怠苔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锅劝,卻和暖如春攒驰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背故爵。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工玻粪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诬垂。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓劲室,卻偏偏與公主長得像,于是被迫代替她去往敵國和親结窘。 傳聞我的和親對象是個殘疾皇子很洋,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容