前段時(shí)間公司項(xiàng)目的需要連接藍(lán)牙設(shè)備挠锥,我們這個(gè)藍(lán)牙設(shè)備是一個(gè)藍(lán)牙手柄,相當(dāng)于是一個(gè)藍(lán)牙外設(shè)鍵盤侨赡,所以是屬于HID設(shè)備蓖租。剛開始也不知道還有HID藍(lán)牙設(shè)備,所以就按照普通藍(lán)牙設(shè)備連接羊壹。翻找了好久的資料蓖宦,才一點(diǎn)一點(diǎn)做好。
HID 是Human Interface Device的縮寫舶掖,由其名稱可以了解HID設(shè)備是直接與人交互的設(shè)備球昨,例如鍵盤、鼠標(biāo)與游戲桿等眨攘。不過(guò)HID設(shè)備并不一定要有人機(jī)接口,只要符合HID類別規(guī)范的設(shè)備都是HID設(shè)備嚣州。
android4.1.2中的Bluetooth應(yīng)用中沒(méi)有hid的相關(guān)代碼鲫售,而android4.2源碼中Bluetooth應(yīng)用中才有hid的代碼。
重點(diǎn)提一句该肴,在實(shí)際使用中情竹,其實(shí)設(shè)備的配對(duì)連接都不難,但是一定要開啟子線程進(jìn)行藍(lán)牙設(shè)備的開關(guān)匀哄、配對(duì)和連接秦效。不然很容易出現(xiàn)一堆異常。涎嚼。阱州。。
使用藍(lán)牙之前法梯,我們需要在AndroidManifest.xml
里邊申請(qǐng)三個(gè)權(quán)限苔货,因?yàn)槲易龅乃闶窍到y(tǒng)級(jí)的軟件,就沒(méi)有動(dòng)態(tài)申請(qǐng)權(quán)限立哑。
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
這里說(shuō)一下夜惭,剛開始遇到的第一個(gè)坑,就是很多博客里邊只講了前兩個(gè)權(quán)限铛绰,而沒(méi)有說(shuō)第三個(gè)權(quán)限诈茧。
1.首先是藍(lán)牙設(shè)備的打開和關(guān)閉
在了解藍(lán)牙開關(guān)之前,我們首先要知道藍(lán)牙適配器的類BluetoothAdapter
捂掰,這個(gè)類的主要功能就是:
- 開關(guān)藍(lán)牙
- 掃描藍(lán)牙設(shè)備
- 設(shè)置/獲取藍(lán)牙狀態(tài)信息敢会,例如:藍(lán)牙狀態(tài)值镊叁、藍(lán)牙Name、藍(lán)牙Mac地址等走触;
直接上代碼:
//獲取BluetoothAdapter晦譬,如果獲取的BluetoothAdapter為空,則表示當(dāng)前設(shè)備不支持藍(lán)牙
BluetoothManager bluetoothManager = (BluetoothManager) SPApplication.getInstance().getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager != null) {
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
} else {
ToastUtils.showShort("藍(lán)牙暫時(shí)不可用");
finishDelegate();
}
//判斷藍(lán)牙是否打開
if (!mBluetoothAdapter.isEnabled()) {
//藍(lán)牙設(shè)備沒(méi)有打開
//打開藍(lán)牙設(shè)備互广,這一步應(yīng)該放到線程里邊操作
mBluetoothAdapter.enable();
} else {
//藍(lán)牙設(shè)備已經(jīng)打開敛腌,需要關(guān)閉藍(lán)牙
//這一步也需要放到線程中操作
mBluetoothAdapter.disable();
}
2.開始進(jìn)行藍(lán)牙的掃描
藍(lán)牙的掃描是使用BluetoothAdapter
對(duì)象中的方法startDiscovery()
,不過(guò)首先我們需要注冊(cè)一個(gè)廣播接收者,接收藍(lán)牙掃描的數(shù)據(jù)惫皱。
1.廣播接收者:
public class BluetoothReciever extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
BluetoothDevice device;
// 搜索發(fā)現(xiàn)設(shè)備時(shí)像樊,取得設(shè)備的信息;注意旅敷,這里有可能重復(fù)搜索同一設(shè)備
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
//這里就是我們掃描到的藍(lán)牙設(shè)備
device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
}
//狀態(tài)改變時(shí)
else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
switch (device.getBondState()) {
case BluetoothDevice.BOND_BONDING://正在配對(duì)
Log.d("BlueToothTestActivity", "正在配對(duì)......");
break;
case BluetoothDevice.BOND_BONDED://配對(duì)結(jié)束
Log.d("BlueToothTestActivity", "完成配對(duì)");
break;
case BluetoothDevice.BOND_NONE://取消配對(duì)/未配對(duì)
Log.d("BlueToothTestActivity", "取消配對(duì)");
default:
break;
}
}
}
}
創(chuàng)建并注冊(cè)藍(lán)牙掃描的廣播接收者:
// 初始化廣播接收者
mBroadcastReceiver = new BluetoothReciever();
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");
//這里我是用全局上下文注冊(cè)廣播接收者生棍,防止在解注冊(cè)的時(shí)候出錯(cuò)
SPApplication.getInstance().registerReceiver(mBroadcastReceiver, intentFilter);
開始進(jìn)行藍(lán)牙掃描
mBluetoothAdapter.startDiscovery();
開始掃描之后,廣播接收者中就會(huì)出現(xiàn)我們掃描到的設(shè)備,設(shè)備信息存放在BluetoothDevice
對(duì)象中媳谁,我們可以將這個(gè)對(duì)象存放到列表中涂滴,然后取出里邊的信息展示到界面上。
3.藍(lán)牙設(shè)備的配對(duì)和連接過(guò)程
HID藍(lán)牙設(shè)備在配對(duì)成功之后還要進(jìn)行連接這一步操作晴音,而普通的藍(lán)牙設(shè)備只需要配對(duì)這一步柔纵。順便提一句,藍(lán)牙設(shè)備的配對(duì)和連接需要放到子線程中操作锤躁。
對(duì)其中的連接操作以及BluetoothProfile的我也不是很理解搁料,這里可以參考Android HID設(shè)備的連接,這篇博客中對(duì)連接的步驟講的還是很清楚的系羞。
這里我把藍(lán)牙設(shè)備的配對(duì)和連接郭计,我寫到了Runnable
的復(fù)寫類里邊,方便放到子線程中使用椒振。
public class BlueConnectThread implements Runnable {
private Handler handler;
private BluetoothDevice device;
private BluetoothAdapter mAdapter;
public BlueConnectThread(Handler handler, BluetoothDevice device, BluetoothAdapter adapter) {
this.handler = handler;
this.device = device;
this.mAdapter = adapter;
}
@Override
public void run() {
//進(jìn)行HID藍(lán)牙設(shè)備的配對(duì)昭伸,配對(duì)成功之后才進(jìn)行下邊的操作
if (pair(device)) {
try {
//進(jìn)行HID藍(lán)牙設(shè)備的連接操作,并返回連接結(jié)果杠人,這里邊的第3個(gè)參數(shù)就是代表連接HID設(shè)備勋乾,
boolean profileProxy = mAdapter.getProfileProxy(SPApplication.getInstance(), connect, 4);
Thread.sleep(3000);
Message message = handler.obtainMessage(BluetoothDialog.CONNECT_BLUE);
//通過(guò)handler將連接結(jié)果和連接的代理返回主線程中。這個(gè)BluetoothProfile對(duì)象在后邊還有用嗡善,所以要返回主線程中
if (profileProxy) {
message.arg1=1;
}else {
message.arg1=2;
}
message.obj = proxys;
handler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 配對(duì)
*
* @param bluetoothDevice
*/
public boolean pair(BluetoothDevice bluetoothDevice) {
device = bluetoothDevice;
Method createBondMethod;
try {
createBondMethod = BluetoothDevice.class.getMethod("createBond");
createBondMethod.invoke(device);
return true;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
/**
*個(gè)人理解是獲取當(dāng)前設(shè)備設(shè)備是否支持INPUT_DEVICE設(shè)備(HID設(shè)備)
*/
public static int getInputDeviceHiddenConstant() {
Class<BluetoothProfile> clazz = BluetoothProfile.class;
for (Field f : clazz.getFields()) {
int mod = f.getModifiers();
if (Modifier.isStatic(mod) && Modifier.isPublic(mod)
&& Modifier.isFinal(mod)) {
try {
if (f.getName().equals("INPUT_DEVICE")) {
return f.getInt(null);
}
} catch (Exception e) {
}
}
}
return -1;
}
private BluetoothProfile proxys;
/**
* 查看BluetoothInputDevice源碼辑莫,connect(BluetoothDevice device)該方法可以連接HID設(shè)備,但是查看BluetoothInputDevice這個(gè)類
* 是隱藏類罩引,無(wú)法直接使用各吨,必須先通過(guò)BluetoothProfile.ServiceListener回調(diào)得到BluetoothInputDevice,然后再反射connect方法連接
*/
private BluetoothProfile.ServiceListener connect = new BluetoothProfile.ServiceListener() {
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
//BluetoothProfile proxy這個(gè)已經(jīng)是BluetoothInputDevice類型了
try {
proxys = proxy;
if (profile == getInputDeviceHiddenConstant()) {
if (device != null) {
//得到BluetoothInputDevice然后反射connect連接設(shè)備
@SuppressLint("PrivateApi") Method method = proxy.getClass().getDeclaredMethod("connect",
new Class[]{BluetoothDevice.class});
method.invoke(proxy, device);
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(int profile) {
}
};
}
4.關(guān)閉當(dāng)前界面
需要注銷廣播接收者的注冊(cè)
@Override
public void onPause() {
super.onPause();
SPApplication.getInstance().unregisterReceiver(mBroadcastReceiver);
}
解除連接的代理(暫時(shí)這樣理解)
做這一步的原因是因?yàn)槊看瓮顺鏊{(lán)牙界面都會(huì)出現(xiàn)錯(cuò)誤,
android.app.ServiceConnectionLeaked: android.app.ServiceConnectionLeaked: Activity.com.xxxxxx.bluetoothconnect.MainActivity has leaked ServiceConnection android.bluetooth.BluetoothInputDevice$2@fd389e1 that was originally bound here
這揭蜒,横浑,,這好像是連接那一步綁定了服務(wù)屉更,在關(guān)閉界面的時(shí)候沒(méi)有解綁服務(wù)徙融。那我們只需要找到在哪開啟了什么服務(wù),再找到解綁這個(gè)服務(wù)的方法就行了瑰谜。
然后我就找到HID設(shè)備連接的方法getProfileProxy(SPApplication.getInstance(), connect, 4)
欺冀,進(jìn)到源碼中查看,被我發(fā)現(xiàn)了BluetoothAdapter
里邊的closeProfileProxy(int profile, BluetoothProfile proxy)
這個(gè)方法萨脑,這個(gè)方法在源碼中的注釋是
/**
* Close the connection of the profile proxy to the Service.
*
* <p> Clients should call this when they are no longer using
* the proxy obtained from {@link #getProfileProxy}.
* Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET} or
* {@link BluetoothProfile#A2DP}
*
* @param profile
* @param proxy Profile proxy object
*/
public void closeProfileProxy(int profile, BluetoothProfile proxy)
憑借我英語(yǔ)亞四級(jí)的水平隐轩,我們可以知道這個(gè)方法就是關(guān)閉服務(wù)的,這個(gè)服務(wù)跟連接有某種不可告人的關(guān)系渤早。职车。。
那我們就直接在關(guān)閉界面的時(shí)候使用就行了鹊杖。注意悴灵,經(jīng)驗(yàn)告訴我這個(gè)方法也需要在子線程中使用。
剛才從子線程中傳回來(lái)的BluetoothProfile
對(duì)象在這里就能用到了仅淑。
@Override
public void onDestroyView() {
super.onDestroyView();
if (proxy != null) {
ThreadPoolProxyFactory.getNormalThreadPoolProxy().execute(new Runnable() {
@Override
public void run() {
mBluetoothAdapter.closeProfileProxy(4, proxy);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
-----------結(jié)束称勋!
這里我只做了藍(lán)牙開關(guān),掃描涯竟,連接配對(duì)的操作,關(guān)于藍(lán)牙信號(hào)的接受和發(fā)送沒(méi)有做空厌。因?yàn)闆](méi)有需求啊庐船,哈哈哈。
有時(shí)間我再把源碼整理出來(lái)嘲更。
里邊的線程池參考Android線程池得要這么用