最近在項(xiàng)目中需要實(shí)現(xiàn)一個(gè)藍(lán)牙搖一搖開門的功能,藍(lán)牙和門口機(jī)數(shù)據(jù)交互的協(xié)議已經(jīng)給定,考慮到藍(lán)牙的適配性,決定使用低功耗藍(lán)牙進(jìn)行開發(fā)。低功耗藍(lán)牙是Android 4.3開始支持的,其主要特點(diǎn)是主要特點(diǎn)是快速搜索,快速連接,超低功耗保持連接和數(shù)據(jù)傳輸,常被用在可穿戴設(shè)備中。
1. 開發(fā)之前的準(zhǔn)備工作
在進(jìn)行BLE(Bluetooth Low Energy)開發(fā)之前,我們先來(lái)了解一波BLE數(shù)據(jù)傳輸涉及到的關(guān)鍵概念:
Generic Attribute Profile (GATT)
通過(guò)BLE連接,讀寫屬性類小數(shù)據(jù)的Profile通用規(guī)范?,F(xiàn)在所有的BLE應(yīng)用Profile都是基于GATT的。
Attribute Protocol (ATT)
GATT是基于ATT Protocol的。ATT針對(duì)BLE設(shè)備做了專門的優(yōu)化,具體就是在傳輸過(guò)程中使用盡量少的數(shù)據(jù)。
每個(gè)屬性都有一個(gè)唯一的UUID,屬性將以characteristics and services的形式傳輸。
Characteristic
Characteristic可以理解為一個(gè)數(shù)據(jù)類型,它包括一個(gè)value和0至多個(gè)對(duì)次value的描述(Descriptor)。
Descriptor
對(duì)Characteristic的描述,例如范圍、計(jì)量單位等。
Service
Characteristic的集合。例如一個(gè)service叫做“Heart Rate Monitor”,
它可能包含多個(gè)Characteristics,其中可能包含一個(gè)叫做“heart rate measurement"的Characteristic。
2. 開發(fā)需要的權(quán)限
Android使用藍(lán)牙,需要聲明BLUETOOTH權(quán)限,如果需要掃描設(shè)備或者操作藍(lán)牙設(shè)置,則還需要BLUETOOTH_ADMIN權(quán)限,如果Android版本在5.0以上,藍(lán)牙還需要位置權(quán)限。

3. 藍(lán)牙的開啟和藍(lán)牙狀態(tài)的監(jiān)聽
在藍(lán)牙編程中,首先我們要知道如何去開啟和關(guān)閉藍(lán)牙,下面介紹兩種方法:
1) 通過(guò)獲取藍(lán)牙管理服務(wù)和適配器來(lái)開啟、關(guān)閉藍(lán)牙,獲取當(dāng)前藍(lán)牙是否為可用狀態(tài);

2) 跳轉(zhuǎn)到系統(tǒng)藍(lán)牙設(shè)置界面,由用戶自行開啟藍(lán)牙,并通過(guò)廣播監(jiān)聽藍(lán)牙開啟與關(guān)閉的通知,通過(guò)藍(lán)牙適配器來(lái)獲取當(dāng)前藍(lán)牙是否為可用狀態(tài):
跳轉(zhuǎn)藍(lán)牙設(shè)置界面

注冊(cè)藍(lán)牙狀態(tài)廣播監(jiān)聽

藍(lán)牙廣播監(jiān)聽,可以接受藍(lán)牙開啟,關(guān)閉時(shí)的通知:

藍(lán)牙是否開啟的判斷

第一種方法可以用來(lái)直接開關(guān)藍(lán)牙,如果是Android 6.0系統(tǒng)及以上的還需要添加動(dòng)態(tài)申請(qǐng)權(quán)限代碼;第二種方法是引導(dǎo)用戶進(jìn)入設(shè)置界面進(jìn)行手動(dòng)設(shè)置,再通過(guò)監(jiān)聽開關(guān)狀態(tài)來(lái)執(zhí)行后續(xù)操作,不需要?jiǎng)討B(tài)申請(qǐng)權(quán)限;
4. 藍(lán)牙設(shè)備的掃描和連接
1)啟動(dòng)藍(lán)牙后,我們就要開始使用藍(lán)牙進(jìn)行掃描,發(fā)現(xiàn)我們想要連接的設(shè)備。在Android5.0版本時(shí),Google對(duì)BLE更新了API,添加了新的掃描方法和回調(diào),在下面代碼中會(huì)進(jìn)行區(qū)分:
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
開始掃描
if(Build.VERSION.SDK_INT >= 21){
mBluetoothLeScanner.startScan(mScanCallback);
}else{
mBluetoothAdapter.startLeScan(mLeScanCallback);
}
關(guān)閉掃描
if(Build.VERSION.SDK_INT >= 21){
mBluetoothLeScanner.stopScan(mScanCallback);
}else{
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
掃描回調(diào)也不相同

2) 掃描找到想要連接的設(shè)備后,就可以開始進(jìn)行設(shè)備連接了:

注意:根據(jù)需要決定是否對(duì)有連接過(guò)的設(shè)備進(jìn)行重新連接。比如有些設(shè)備在連接通訊后就自行斷開,連接總時(shí)長(zhǎng)只有幾秒鐘的,這樣的不需要保留地址,每次都直接連接比較好。
連接設(shè)備時(shí),會(huì)觸發(fā)一個(gè)通信的回調(diào),這個(gè)回調(diào)比較重要,在設(shè)備連接,數(shù)據(jù)通信中都會(huì)用到:

在連接狀態(tài)回調(diào)中判斷連接狀態(tài),連接成功后,調(diào)用gatt.discoverServices()方法去發(fā)現(xiàn)連接的設(shè)備提供的服務(wù)及其特征和描述符;發(fā)現(xiàn)成功后,會(huì)調(diào)用onServicesDiscovered()回調(diào),表示可以與之通信了;

發(fā)現(xiàn)服務(wù)后,通過(guò)handler回調(diào)到主線程,執(zhí)行了下面的方法用來(lái)設(shè)置后續(xù)需要用到連接的服務(wù)節(jié)點(diǎn)下的Characteristic發(fā)生改變時(shí)進(jìn)行通知:

此處是獲取了開門服務(wù),設(shè)置開門服務(wù)下接受到命令返回的Characteristic

setCharacteristicNotification()方法會(huì)觸發(fā)onCharacteristicChanged()回調(diào)。接受到onCharacteristicChanged()回調(diào)后證明可以設(shè)置通知成功,可以開始發(fā)送通訊指令了。

5. 藍(lán)牙的數(shù)據(jù)發(fā)送和接收
經(jīng)過(guò)4中的步驟后,此時(shí)遠(yuǎn)程設(shè)備已經(jīng)準(zhǔn)備好進(jìn)行藍(lán)牙通訊了,下面開始發(fā)送指令:

此處是向開門服務(wù)下的接收命令的BluetoothGattCharacteristic對(duì)象寫入命令數(shù)據(jù)。

writeCharacteristic()方法會(huì)觸發(fā)onCharacteristicWrite()回調(diào)

注意:BLE向設(shè)備寫入數(shù)據(jù)時(shí),最大20byte,如果需要寫入的數(shù)據(jù)大于20byte,可以選擇將數(shù)據(jù)進(jìn)行分割,先發(fā)送前20byte,利用回調(diào)觸發(fā)將后續(xù)數(shù)據(jù)依次發(fā)送,每次最多20byte。
遠(yuǎn)程設(shè)備接收到完整指令并執(zhí)行后,特征值變化會(huì)觸發(fā)onCharacteristicChanged()回調(diào),并傳回?cái)?shù)據(jù)

這樣,一個(gè)完整的通信循環(huán)就完成了。
6. 總結(jié)
藍(lán)牙BLE本身的流程并不復(fù)雜,關(guān)鍵在于存在有許多特殊情況需要處理,下面對(duì)特殊情況的處理進(jìn)行敘述和總結(jié):
1、 調(diào)用gatt.discoverServices()方法后不執(zhí)行onServiceDiscovered()回調(diào),這種問題有兩種情況,一種是設(shè)備距離遠(yuǎn),回調(diào)時(shí)間過(guò)長(zhǎng),一種是并沒有真正連接到設(shè)備,可以嘗試將gatt.discoverServices()方法放到主線程中執(zhí)行;
2、 onServicesDiscovered 回調(diào)里不能直接執(zhí)行 write /readDataFromCharacteristic() 或者 enableNotificationOfCharacteristic之類的,而要放到主線程里執(zhí)行,可以使用 handler轉(zhuǎn)換線程;
3、 掃描盡量不要放在主線程進(jìn)行,可以放入子線程中,部分機(jī)型會(huì)出現(xiàn)do too many work in main thread。
4、 任何出錯(cuò),超時(shí),用完就馬上調(diào)用Gatt.disconnect(), Gatt.close()。