外圍輸入設備,例如:藍牙鍵盤瞻讽,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
我們直接來看看是怎么用的
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)處理