Android ble藍(lán)牙開發(fā)
BLE介紹
安卓4.3(API 18)
為BLE的核心功能提供平臺(tái)支持和API蛀蜜,App可以利用它來發(fā)現(xiàn)設(shè)備、查詢服務(wù)和讀寫特性增蹭。相比傳統(tǒng)的藍(lán)牙滴某,BLE更顯著的特點(diǎn)是低功耗。這一優(yōu)點(diǎn)使Android App
可以與具有低功耗要求的BLE設(shè)備通信滋迈,如近距離傳感器霎奢、心臟速率監(jiān)視器、健身設(shè)備等饼灿。
BLE開發(fā)
BLE權(quán)限添加
為了在app中使用藍(lán)牙功能幕侠,必須聲明藍(lán)牙權(quán)限BLUETOOTH
。利用這個(gè)權(quán)限去執(zhí)行藍(lán)牙通信碍彭,例如請(qǐng)求連接晤硕、接受連接、和傳輸數(shù)據(jù)硕旗。如果想讓你的app啟動(dòng)設(shè)備發(fā)現(xiàn)或操縱藍(lán)牙設(shè)置窗骑,必須聲明BLUETOOTH_ADMIN
權(quán)限女责。注意:如果你使用BLUETOOTH_ADMIN
權(quán)限漆枚,你也必須聲明BLUETOOTH
權(quán)限。在你的app manifest
文件中聲明藍(lán)牙權(quán)限抵知。
設(shè)置BLE
你的app能與BLE通信之前墙基,你需要確認(rèn)設(shè)備是否支持BLE软族,如果支持,確認(rèn)已經(jīng)啟用残制。雖然現(xiàn)在的手機(jī)基本都支持BLE立砸,但是考慮到程序的健碩性,如果設(shè)置為false,這個(gè)檢查是必需的初茶。
BluetoothAdapter類介紹
獲取:所有的藍(lán)牙活動(dòng)都需要藍(lán)牙適配器颗祝。BluetoothAdapter
代表設(shè)備本身的藍(lán)牙適配器(藍(lán)牙無線)。整個(gè)系統(tǒng)只有一個(gè)藍(lán)牙適配器恼布,而且你的app使用它與系統(tǒng)交互螺戳。下面的代碼片段顯示了如何得到適配器。注意該方法使用getSystemService()
返回BluetoothManager
折汞,然后將其用于獲取適配器的一個(gè)實(shí)例倔幼。Android 4.3(API 18)
引入BluetoothManager
。
// 初始化藍(lán)牙適配器
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
有了mBluetoothAdapter
之后就可以判斷當(dāng)前藍(lán)牙開關(guān)狀態(tài)爽待、藍(lán)牙未開啟情況下代碼里面自動(dòng)開啟藍(lán)牙损同、以及掃描周邊的ble設(shè)備
開啟藍(lán)牙
接下來,你需要確認(rèn)藍(lán)牙是否開啟鸟款。調(diào)用isEnabled()
去檢測(cè)藍(lán)牙當(dāng)前是否開啟膏燃。如果該方法返回false,藍(lán)牙被禁用。下面的代碼檢查藍(lán)牙是否開啟欠雌,如果沒有開啟蹄梢,可以提示用戶去設(shè)置開啟藍(lán)牙。
// 確保藍(lán)牙在設(shè)備上可以開啟
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
//藍(lán)牙未開啟
}
發(fā)現(xiàn)BLE設(shè)備
為了發(fā)現(xiàn)BLE設(shè)備富俄,使用startLeScan()
方法禁炒。這個(gè)方法需要一個(gè)參數(shù)BluetoothAdapter.LeScanCallback
。你必須實(shí)現(xiàn)它的回調(diào)函數(shù)霍比,那就是返回的掃描結(jié)果幕袱。因?yàn)閽呙璺浅O碾娏浚銘?yīng)當(dāng)遵守以下準(zhǔn)則:
1·只要找到所需的設(shè)備悠瞬,停止掃描们豌。
2·不要在循環(huán)里掃描,并且對(duì)掃描設(shè)置時(shí)間限制浅妆。以前可用的設(shè)備可能已經(jīng)移出范圍望迎,繼續(xù)掃描消耗電池電量。
以下代碼顯示如何掃描設(shè)備和停止掃描設(shè)備
// 10秒后停止尋找.
private static final long SCAN_PERIOD = 10000;
private void scanLeDevice(final boolean enable) {
if (enable) {
// 經(jīng)過預(yù)定掃描期后停止掃描
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
//停止掃描
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
如果你只想掃描指定類型的外圍設(shè)備凌外,可以改為調(diào)用startLeScan(UUID[], BluetoothAdapter.LeScanCallback)
,需要提供你的app支持的GATT services
的UUID
對(duì)象數(shù)組辩尊。
掃描的信息在LeScallCallback
里面返回
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
//device 里面包含設(shè)備的mac地址和設(shè)備的名稱
//scanRecord里面就是ble設(shè)備發(fā)出的廣播包數(shù)據(jù)
//rssi表示ble設(shè)備的信號(hào)值,該值為負(fù)數(shù)康辑,值越大表示信號(hào)值越好
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
//如果操作UI摄欲,切換主線程
runOnUiThread(new Runnable() {
@Override
public void run() {
...
}
});
}
};
連接到GATT服務(wù)端
與一個(gè)BLE
設(shè)備交互的第一步就是連接它——更具體的轿亮,連接到BLE
設(shè)備上的GATT
服務(wù)端。為了連接到BLE
設(shè)備上的GATT
服務(wù)端胸墙,需要使用connectGatt()
方法我注。這個(gè)方法需要三個(gè)參數(shù):一個(gè)Context
對(duì)象,自動(dòng)連接(boolean值,表示只要BLE設(shè)備可用是否自動(dòng)連接到它)迟隅,和BluetoothGattCallback
調(diào)用但骨。
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
連接到GATT服務(wù)端時(shí),由BLE設(shè)備做主機(jī)智袭,并返回一個(gè)BluetoothGatt實(shí)例嗽冒,然后你可以使用這個(gè)實(shí)例來進(jìn)行GATT客戶端操作。請(qǐng)求方(Android app)是GATT客戶端补履。BluetoothGattCallback用于傳遞結(jié)果給用戶添坊,例如連接狀態(tài),以及任何進(jìn)一步GATT客戶端操作箫锤。
private final BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {//當(dāng)連接狀態(tài)發(fā)生改變
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {//當(dāng)藍(lán)牙設(shè)備已經(jīng)連接
//獲取ble設(shè)備上面的服務(wù)
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {//當(dāng)設(shè)備 無法連接
}
}
//調(diào)用discoverServices后的回調(diào)
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取服務(wù)成功
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
// 讀寫特性
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
}
}
...
};
發(fā)送數(shù)據(jù)
首先通過UUID
拿到對(duì)應(yīng)的服務(wù)贬蛙,再通過UUID
拿到服務(wù)的特征,設(shè)置特征的屬性是BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
。設(shè)置成功后可以在該特征值上發(fā)送數(shù)據(jù)到ble設(shè)備和接收ble設(shè)備的數(shù)據(jù)谚攒⊙糇迹看到這里也許各位不熟ble開發(fā)和剛ble開發(fā)的看官也許就一臉懵逼,我只是想發(fā)送數(shù)據(jù)到ble設(shè)備馏臭,怎么一下子搞出個(gè)UUID
服務(wù)和特征值了,難道就不能和B/S開發(fā)一樣野蝇,連接之后我把數(shù)據(jù)發(fā)送到一個(gè)接口,服務(wù)器端就返回我需要的數(shù)據(jù)那么簡(jiǎn)單。這還得從ble
藍(lán)牙的架構(gòu)說起括儒。
BLE分為三部分Service绕沈、Characteristic、Descriptor
帮寻,這三部分都由UUID
作為唯一標(biāo)示符乍狐。一個(gè)藍(lán)牙4.0的終端可以包含多個(gè)Service
,一個(gè)Service
可以包含多個(gè)Characteristic
固逗,一個(gè)Characteristic
包含一個(gè)Value
和多個(gè)Descriptor
浅蚪,一個(gè)Descriptor
包含一個(gè)Value
。service是characteristic
的集合.一個(gè)characteristic
包括一個(gè)單一變量和0-n個(gè)用來描述characteristic
變量的descriptor.Descriptor
用來描述characteristic
變量的屬性烫罩。例如惜傲,一個(gè)descriptor
可以規(guī)定一個(gè)可讀的描述,或者一個(gè)characteristic
變量可接受的范圍贝攒,或者一個(gè)characteristic
變量特定的測(cè)量單位盗誊。一般來說,Characteristic
是手機(jī)與BLE終端交換數(shù)據(jù)的關(guān)鍵.。
舉個(gè)栗子:當(dāng)我們想要用手機(jī)與BLE設(shè)備進(jìn)行通信時(shí)浊伙,實(shí)際上也就相當(dāng)于我們要去找一個(gè)學(xué)生交流,首先我們需要搭建一個(gè)管道长捧,也就是我們需要先獲取得到一個(gè)BluetoothGatt
嚣鄙,其次我們需要知道這個(gè)學(xué)生在哪一個(gè)班級(jí),學(xué)號(hào)是什么串结,這也就是我們所說的serviceUUID
哑子,和charUUID。這里我們還需要注意一下肌割,找到這個(gè)學(xué)生后并不是直接和他交流卧蜓,他就好像一個(gè)中介一樣,在手機(jī)和BLE終端設(shè)備之間幫助這兩者傳遞著信息把敞,我們手機(jī)所發(fā)數(shù)據(jù)要先經(jīng)過他弥奸,在由他傳遞到BLE設(shè)備上,而BLE設(shè)備上的返回信息奋早,也是先傳遞到他那邊盛霎,然后手機(jī)再從他那邊進(jìn)行讀取。
在發(fā)送數(shù)據(jù)之前需先設(shè)置特征的具有notificaion
功能
private BluetoothGatt mBluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
mBluetoothGatt.writeDescriptor(descriptor);
設(shè)置完成后回調(diào)
@Override
public final void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
//設(shè)置成功
if (status == BluetoothGatt.GATT_SUCCESS) {
}
}
設(shè)置成功后就開始發(fā)送數(shù)據(jù)了耽装。
//將指令放置進(jìn)特征中
characteristic.setValue(data);
//設(shè)置回復(fù)形式characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
//開始寫數(shù)據(jù)
mBluetoothGatt.writeCharacteristic(chharacteristic);
寫入數(shù)據(jù)成功后回調(diào)愤炸,在BluetoothGattCallback里面
protected void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
//發(fā)送數(shù)據(jù)成功啦啦啦
}
如何設(shè)備回復(fù)數(shù)據(jù)則會(huì)回調(diào)BluetoothGattCallback
@Override
public final void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
}
關(guān)閉客戶端App
當(dāng)你的app
完成BLE
設(shè)備的使用后,應(yīng)該調(diào)用close()
掉奄,系統(tǒng)可以合理釋放占用資源规个。
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
最后分享我在BLE 開發(fā)中遇到的坑和一些經(jīng)驗(yàn)
1 在所有藍(lán)牙的回調(diào)中不要操作UI。我是不會(huì)告訴你我是怎么發(fā)現(xiàn)這個(gè)坑的姓建。
2 在所有的藍(lán)牙回調(diào)中不要執(zhí)行耗時(shí)操作诞仓。
3 發(fā)送數(shù)據(jù)要等到上一條數(shù)據(jù)發(fā)送成功后再發(fā)下一條數(shù)據(jù),畢竟BLE設(shè)備運(yùn)算沒有手機(jī)快速兔,這里可以推薦一個(gè)nodic的開源藍(lán)牙連接nRf工具,里面非常好的對(duì)發(fā)送的數(shù)據(jù)做了一個(gè)數(shù)據(jù)隊(duì)列狂芋。
4 合理的控制掃描過程,一般出現(xiàn)133錯(cuò)誤的時(shí)候重連就可以先去掃描再去連接憨栽。若掃描不到時(shí)不要馬上又去掃描帜矾,不然你把手機(jī)放那一夜,把設(shè)備遠(yuǎn)離它屑柔,第二天回來看手機(jī)時(shí)會(huì)驚喜的發(fā)現(xiàn)手機(jī)沒電自動(dòng)關(guān)機(jī)了
遇到的坑
1 斷線重連的時(shí)候總是報(bào)133錯(cuò)誤,
斷線后不要馬上去連接.先掃描設(shè)備屡萤,掃描到設(shè)備后再去連接。
2 掃描不到設(shè)備
手動(dòng)關(guān)閉藍(lán)牙再打開藍(lán)牙開關(guān)掸宛。這個(gè)可能是重連里面的掃描引起的死陆,如果設(shè)備未在周邊,一直去掃描的話,后來設(shè)備在身邊也可能掃描不到設(shè)備。如果未能連接設(shè)備措译,也不能一直去掃描别凤。掃描不到設(shè)備時(shí)說明設(shè)備并不到周邊,可以延遲多少時(shí)間后再去掃描
3 連接設(shè)備后發(fā)送數(shù)據(jù)领虹,發(fā)送數(shù)據(jù)的回調(diào)函數(shù)也已經(jīng)走了规哪。沒有接收到數(shù)據(jù)
查看設(shè)置特征值的描述值
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
mBluetoothGatt.writeDescriptor(descriptor)的回調(diào)里面是不是回調(diào)成功了
4反復(fù)斷開藍(lán)牙后再重連導(dǎo)致連接失敗
斷開藍(lán)牙后應(yīng)該調(diào)用close()方法釋放資源.連接時(shí)應(yīng)該設(shè)置超時(shí),在超時(shí)時(shí)間內(nèi)繼續(xù)去連接,基本低塌衰、中诉稍、高端機(jī)都能重新連接上。
5 連接上之后自動(dòng)斷開連接最疆,重連上之后又自動(dòng)斷開連接杯巨,如此反復(fù)。
我們的BLE設(shè)備在某些低端機(jī)會(huì)遇到這種問題努酸。聽固件工程師說是BLE設(shè)備藍(lán)牙芯片頻率和手機(jī)藍(lán)牙頻率問題服爷,需調(diào)BLE設(shè)備頻率。遇到這種問題APP就束手無策了获诈。
6 反復(fù)操作斷開和連接導(dǎo)致系統(tǒng)藍(lán)牙掛掉(無響應(yīng))
基本也是沒有合理釋放資源導(dǎo)致
7 調(diào)用掃描操作導(dǎo)致APP無響應(yīng)
查看系統(tǒng)藍(lán)牙是否掛掉了层扶。基本和問題6類似