Android 蓝牙Hid开发
原文地址: https://blog.csdn.net/VNanyesheshou/article/details/61914974
Demo下載:http://www.demodashi.com/demo/13891.html
最近客戶需求,android系統的一個設備,想連接一個掃描槍(類似藍牙鍵盤,只支持hid協議),并且可以收到其數據,填寫到輸入框中。我這里借了一個藍牙鼠標,用來與android設備連接。
1 Hid簡介
HID設備(Hunman Interface Device Profile),即人機交互設備,常見的有鼠標,鍵盤,游戲手柄,等等。一般有線方式都是通過USB連線連接到機器設備,作為用戶輸入設備。在藍牙技術中,HID設備的接入就是無線的了。
網上查資料說hid從android4.0開始支持(可能是usb hid),不過藍牙hid應該從android4.2開始支持的,如下圖所示:
android4.1.2中的Bluetooth應用中沒有hid的相關代碼,而android4.2源碼中Bluetooth應用中才有hid的代碼。
2 主要代碼
連接hid設備步驟:
2.1 開啟藍牙,通過廣播接收者監聽藍牙相關狀態。
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) {Toast.makeText(this, "不支持藍牙功能", 0).show();// 不支持藍牙return; } // 如果沒有打開藍牙 if (!mBluetoothAdapter.isEnabled()) {mBluetoothAdapter.enable(); } // 初始化廣播接收者 mBroadcastReceiver = new BlueBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothDevice.ACTION_FOUND); intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); intentFilter.addAction("android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"); this.registerReceiver(mBroadcastReceiver, intentFilter);2.2 獲得inputDevice profile
// 4.2以上才支持HID模式
if (Build.VERSION.SDK_INT >= 17) {mHidUtil = HidUtil.getInstance(this);} public static HidUtil getInstance(Context context){if(null == instance){instance = new HidUtil(context);}return instance; }private HidUtil(Context context) {this.context = context;mBtAdapter = BluetoothAdapter.getDefaultAdapter();try {mBtAdapter.getProfileProxy(context,mListener, INPUT_DEVICE);} catch (Exception e) {e.printStackTrace();} }通過BluetoothAdapter對象調用getProfileProxy()函數獲取代理藍牙輸入設備代理對象。
其中參數mListener必須實現BluetoothProfile.ServiceListener()。獲取代理對象成功或失敗都會回調該Listener。
private BluetoothProfile.ServiceListener mListener = new BluetoothProfile.ServiceListener() {@Overridepublic void onServiceConnected(int profile, BluetoothProfile proxy) {Log.i(TAG, "mConnectListener onServiceConnected");//BluetoothProfile proxy這個已經是BluetoothInputDevice類型了try {if (profile == INPUT_DEVICE) {mBluetoothProfile = proxy;}} catch (Exception e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(int profile) {Log.i(TAG, "mConnectListener onServiceConnected");} };當連接代理服務成功,回調onServiceConnected()函數,失敗則回調onServiceDisconnected()函數。
其中onServiceConnected()中的參數proxy類型為BluetoothProfile,這里因為獲取BluetoothHeadset、BluetoothA2dp對象也要使用該回調函數,所以參數類型設置為BluetoothInputDevice、BluetoothHeadset、BluetoothA2dp的父類。這里可以將其轉換成BluetoothInputDevice類型(BluetoothInputDevice是BluetoothProfile的子類)。
獲取到輸入設備的代理對象,之后就可以進行連接操作了。
2.3 掃描(點擊連接按鈕開始掃描藍牙設備)
mBluetoothAdapter.startDiscovery();2.4 配對
廣播接收者監聽掃描到藍牙設備,過濾出所需藍牙設備進行配對,如果之前配對過則直接連接。
if(action.equals(BluetoothDevice.ACTION_FOUND)){// 通過廣播接收到了BluetoothDevicefinal BluetoothDevice device = (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);if (device == null) return;String btname = device.getName();String address = device.getAddress();Log.i(TAG, "bluetooth name:"+btname+",address:"+address);if((address != null&& address.equals(HID_ADDR))||(btname != null && btname.equals(HID_NAME))){mConnectDevice = device;mBluetoothAdapter.cancelDiscovery();if(!mHidUtil.isBonded(device)){//先配對mHidUtil.pair(device);}else {//已經配對則直接連接mHidUtil.connect(device);}} }HidUtil類中的配對方法:
/*** 配對* @param BluetoothDevice*/ public void pair(BluetoothDevice device) {Log.i(TAG, "pair device:"+device);Method createBondMethod;try {createBondMethod = BluetoothDevice.class.getMethod("createBond");createBondMethod.invoke(device);} catch (Exception e) {e.printStackTrace();}}2.5 連接(配對成功后)
if(action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)){BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);String name = device.getName();String address = device.getAddress();Log.i(TAG,"name:"+name+",address:"+address+",bondstate:"+device.getBondState());if((address != null&& address.equals(HID_ADDR))||(name != null && name.equals(HID_NAME))){if(device.getBondState() == BluetoothDevice.BOND_BONDED)mHidUtil.connect(device);} }判斷是否是要連接的輸入設備,如果符合條件則進行連接。
HidUtil中connect 方法
/*** 連接設備* @param bluetoothDevice*/ public void connect(final BluetoothDevice device) {Log.i(TAG, "connect device:"+device);try {//得到BluetoothInputDevice然后反射connect連接設備Method method = mBluetoothProfile.getClass().getMethod("connect",new Class[] { BluetoothDevice.class });method.invoke(mBluetoothProfile, device);} catch (Exception e) {e.printStackTrace();} }BluetoothInputDevice中的connect方法是隱藏的,所以需要通過反射機制獲取該方法進行調用。
2.6 監聽連接狀態
通過廣播接收者監聽連接狀態。
if(action.equals("android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED")){int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,0);BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);Log.i(TAG,"state="+state+",device="+device);if(state == BluetoothProfile.STATE_CONNECTED){//連接成功Toast.makeText(MainActivity.this, R.string.connnect_success, Toast.LENGTH_SHORT).show();} else if(state == BluetoothProfile.STATE_DISCONNECTED){//連接失敗Toast.makeText(MainActivity.this, R.string.disconnected, Toast.LENGTH_SHORT).show();} }2.7 斷開連接
if(mConnectDevice != null)mHidUtil.disConnect(mConnectDevice);HidUtil中disconnect方法
/*** 斷開連接* @param BluetoothDevice*/ public void disConnect(BluetoothDevice device) {Log.i(TAG, "disConnect device:"+device);try {if (device != null) {Method method = mBluetoothProfile.getClass().getMethod("disconnect",new Class[] { BluetoothDevice.class });method.invoke(mBluetoothProfile, device);}} catch (Exception e) {e.printStackTrace();} }手機端斷開連接后,重新連接會提示“只能有鼠標發起重新連接請求,請使用鼠標重新連接”。
3 接收數據
adb shell
getevent -l
當連接成功后,會看到如下內容:
could not get driver version for /dev/input/mouse1, Not a typewriter
add device 7: /dev/input/event6
name: “Bluetooth Mouse”
這表示藍牙鼠標成為一個輸入設備。
左擊鼠標:
/dev/input/event6: EV_MSC MSC_SCAN 00090001
/dev/input/event6: EV_KEY BTN_MOUSE DOWN
/dev/input/event6: EV_SYN SYN_REPORT 00000000
/dev/input/event6: EV_MSC MSC_SCAN 00090001
/dev/input/event6: EV_KEY BTN_MOUSE UP
/dev/input/event6: EV_SYN SYN_REPORT 00000000
我們應用中打印
03-13 12:08:36.526 I/MainActivity(23670): dispatchTouchEvent ev:MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=269.7555, y[0]=543.9628, toolType[0]=TOOL_TYPE_MOUSE, buttonState=BUTTON_PRIMARY, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=16788085, downTime=16788085, deviceId=39, source=0x2002 }
03-13 12:08:36.653 I/MainActivity(23670): dispatchTouchEvent ev:MotionEvent { action=ACTION_UP, actionButton=0, id[0]=0, x[0]=269.7555, y[0]=543.9628, toolType[0]=TOOL_TYPE_MOUSE, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=16788216, downTime=16788085, deviceId=39, source=0x2002 }
表示在屏幕中某位置處點擊了一下。
右擊鼠標:
/dev/input/event6: EV_MSC MSC_SCAN 00090002
/dev/input/event6: EV_KEY BTN_RIGHT DOWN
/dev/input/event6: EV_SYN SYN_REPORT 00000000
/dev/input/event6: EV_MSC MSC_SCAN 00090002
/dev/input/event6: EV_KEY BTN_RIGHT UP
/dev/input/event6: EV_SYN SYN_REPORT 00000000
03-13 12:09:48.147 I/MainActivity(23670): dispatchKeyEvent event:KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x0, hwFlags=0x0, repeatCount=0, eventTime=16859701, downTime=16859701, deviceId=39, source=0x2002 }
03-13 12:09:48.147 I/MainActivity(23670): onKeyDown keyCode=4
03-13 12:09:48.308 I/InputDispatcher( 996): touch process [23670], send to pg
03-13 12:09:48.327 I/MainActivity(23670): dispatchKeyEvent event:KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x0, hwFlags=0x0, repeatCount=0, eventTime=16859870, downTime=16859870, deviceId=39, source=0x2002 }
03-13 12:09:48.327 I/MainActivity(23670): onKeyUp keyCode=4
03-13 12:09:48.337 I/MainActivity(23670): onBackPressed
表示點擊了一下返回鍵,程序退出。
移動鼠標會發現屏幕上小光標在移動,滑動鼠標也會觸發相應事件。
4 其他
現在大部分手機是支持hid的,并且也將該功能打開狀態。
如果是做系統開發,就需要注意將Bluetooth中的hid開關打開。
將源碼中的packages/apps/Bluetooth/res/values/config.xml的profile_supported_hid 修改為true。
<bool name="profile_supported_hid">true</bool>demo下載地址:http://www.demodashi.com/demo/13891.html
5 Demo測試
測試發現在android 10上運行代碼時出現如下log:
Accessing hidden method Landroid/bluetooth/BluetoothHidHost;->connect(Landroid/bluetooth/BluetoothDevice;)Z (greylist-max-o, reflection, denied)
解決方法:
adb shell settings put global hidden_api_policy 1
參考博客:
https://blog.csdn.net/wangxueming/article/details/99673555
https://zhuanlan.zhihu.com/p/62955960
歡迎關注我的微信公眾號:
總結
以上是生活随笔為你收集整理的Android 蓝牙Hid开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue项目如何放到服务器上,怎么把vue
- 下一篇: 战队第二次考核wp