Android藍牙——HID開發


代碼地址如下:
http://www.demodashi.com/demo/13891.html

原文地址:

https://blog.csdn.net/VNanyesheshou/article/details/61914974

一 環境

開發環境:
 jdk1.6 Eclipse
 or jdk1.8 AS3.0.1
運行環境:
 華為V10(Android8.0)
實現功能:
 Android 藍牙Hid——連接藍牙鼠標、鍵盤等輸入設備。

二 代碼結構

AS

Eclipse

三、代碼

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設備步驟:

  1. 開啟藍牙
  2. 獲得inputDevice profile
  3. 掃描
  4. 配對
  5. 連接

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() {
	@Override
	public 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();
		}
	}
 
	@Override
	public 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)){
    // 通過廣播接收到了BluetoothDevice
    final 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

表示點擊了一下返回鍵,程序退出。

移動鼠標會發現屏幕上小光標在移動,滑動鼠標也會觸發相應事件。
4 其他
現在大部分手機是支持hid的,並且也將該功能打開狀態。

如果是做系統開發,就需要注意將Bluetooth中的hid開關打開。
將源碼中的packages/apps/Bluetooth/res/values/config.xml的profile_supported_hid 修改為true。
true

歡迎關注我的微信公眾號:

Android藍牙——HID開發

代碼地址如下:
http://www.demodashi.com/demo/13891.html

注:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM