因?yàn)樽约旱捻?xiàng)目中有用到了藍(lán)牙相關(guān)的功能,所以之前也斷斷續(xù)續(xù)地針對(duì)藍(lán)牙通信尤其是BLE通信進(jìn)行了一番探索,整理出了一個(gè)開(kāi)源框架FastBle與各位分享經(jīng)驗(yàn)影钉。
源碼地址:
隨著對(duì)FastBle框架關(guān)注的人越來(lái)越多牛郑,與我討論問(wèn)題的小伙伴也多起來(lái)羔挡,所以整理了一篇文章纵顾,詳細(xì)介紹一下框架的用法伍茄,一些坑,還有我對(duì)Android BLE開(kāi)發(fā)實(shí)踐方面的理解施逾。
文章分為3個(gè)部分:
- FastBle的使用
- BLE開(kāi)發(fā)實(shí)踐方面的理解
- FastBle源碼解析
1. FastBle的使用
1.1 聲明權(quán)限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
- android.permission.BLUETOOTH : 這個(gè)權(quán)限允許程序連接到已配對(duì)的藍(lán)牙設(shè)備, 請(qǐng)求連接/接收連接/傳輸數(shù)據(jù)需要改權(quán)限, 主要用于對(duì)配對(duì)后進(jìn)行操作;
- android.permission.BLUETOOTH_ADMIN : 這個(gè)權(quán)限允許程序發(fā)現(xiàn)和配對(duì)藍(lán)牙設(shè)備, 該權(quán)限用來(lái)管理藍(lán)牙設(shè)備, 有了這個(gè)權(quán)限, 應(yīng)用才能使用本機(jī)的藍(lán)牙設(shè)備, 主要用于對(duì)配對(duì)前的操作;
- android.permission.ACCESS_COARSE_LOCATION和android.permission.ACCESS_FINE_LOCATION:Android 6.0以后敷矫,這兩個(gè)權(quán)限是必須的,藍(lán)牙掃描周圍的設(shè)備需要獲取模糊的位置信息汉额。這兩個(gè)權(quán)限屬于同一組危險(xiǎn)權(quán)限曹仗,在清單文件中聲明之后,還需要再運(yùn)行時(shí)動(dòng)態(tài)獲取闷愤。
1.2. 初始化及配置
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BleManager.getInstance().init(getApplication());
BleManager.getInstance()
.enableLog(true)
.setReConnectCount(1, 5000)
.setOperateTimeout(5000);
}
在使用之前整葡,需要事先調(diào)用初始化init(Application app)
方法件余。此外讥脐,可以進(jìn)行一些自定義的配置,比如是否顯示框架內(nèi)部日志啼器,重連次數(shù)和重連時(shí)間間隔旬渠,以及操作超時(shí)時(shí)間。
1.3. 掃描外圍設(shè)備
APP作為中心設(shè)備端壳,想要與外圍硬件設(shè)備建立藍(lán)牙通信的前提是首先得到設(shè)備對(duì)象告丢,途徑是掃描。在調(diào)用掃描方法之前损谦,你首先應(yīng)該先處理下面的準(zhǔn)備工作岖免。
-
判斷當(dāng)前Android設(shè)備是否支持BLE。
Android 4.3以后系統(tǒng)中加入了藍(lán)牙BLE的功能照捡。BleManager.getInstance().isSupportBle();
-
判斷當(dāng)前Android設(shè)備的藍(lán)牙是否已經(jīng)打開(kāi)颅湘。
可以直接調(diào)用下面的判斷方法來(lái)判斷本機(jī)是否已經(jīng)打開(kāi)了藍(lán)牙,如果沒(méi)有栗精,向用戶拋出提示闯参。BleManager.getInstance().isBlueEnable();
-
主動(dòng)打開(kāi)藍(lán)牙。
除了判斷藍(lán)牙是否打開(kāi)給以用戶提示之外悲立,我們也可以通過(guò)程序直接幫助用戶打開(kāi)藍(lán)牙開(kāi)關(guān)鹿寨,打開(kāi)方式有這幾種:
方法1:通過(guò)藍(lán)牙適配器直接打開(kāi)藍(lán)牙。BleManager.getInstance().enableBluetooth();
方法2:通過(guò)
startActivityForResult
引導(dǎo)界面引導(dǎo)用戶打開(kāi)藍(lán)牙薪夕。Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent, 0x01);
需要注意的是脚草,第一種方法是異步的,打開(kāi)藍(lán)牙需要一段時(shí)間原献,調(diào)用此方法后馏慨,藍(lán)牙不會(huì)立刻就處于開(kāi)啟狀態(tài)涩蜘。如果使用此方法后緊接者就需要進(jìn)行掃描,建議維護(hù)一個(gè)阻塞線程熏纯,內(nèi)部每隔一段時(shí)間查詢藍(lán)牙是否處于開(kāi)啟狀態(tài)同诫,外部顯示等待UI引導(dǎo)用戶等待,直至開(kāi)啟成功樟澜。使用第二種方法误窖,會(huì)通過(guò)系統(tǒng)彈出框的形式引導(dǎo)用戶開(kāi)啟,最終通過(guò)
onActivityResult
的形式回調(diào)通知是否開(kāi)啟成功秩贰。 6.0及以上機(jī)型動(dòng)態(tài)獲取位置權(quán)限霹俺。
藍(lán)牙打開(kāi)之后,進(jìn)行掃描之前毒费,需要判斷下當(dāng)前設(shè)備是否是6.0及以上丙唧,如果是,需要?jiǎng)討B(tài)獲取之前在Manifest中聲明的位置權(quán)限觅玻。-
配置掃描規(guī)則
掃描規(guī)則可以配置1個(gè)或多個(gè)想际,也可以不配置使用默認(rèn)(掃描10秒)。掃描的時(shí)候溪厘,會(huì)根據(jù)配置的過(guò)濾選項(xiàng)胡本,對(duì)掃描到的設(shè)備進(jìn)行過(guò)濾,結(jié)果返回過(guò)濾后的設(shè)備畸悬。掃描時(shí)間配置為小于等于0侧甫,會(huì)實(shí)現(xiàn)無(wú)限掃描,直至調(diào)用BleManger.getInstance().cancelScan()
來(lái)中止掃描蹋宦。BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder() .setServiceUuids(serviceUuids) // 只掃描指定的服務(wù)的設(shè)備披粟,可選 .setDeviceName(true, names) // 只掃描指定廣播名的設(shè)備,可選 .setDeviceMac(mac) // 只掃描指定mac的設(shè)備冷冗,可選 .setAutoConnect(isAutoConnect) // 連接時(shí)的autoConnect參數(shù)守屉,可選,默認(rèn)false .setScanTimeOut(10000) // 掃描超時(shí)時(shí)間贾惦,可選胸梆,默認(rèn)10秒 .build(); BleManager.getInstance().initScanRule(scanRuleConfig);
以上準(zhǔn)備工作完成后,就可以開(kāi)始進(jìn)行掃描须板。
BleManager.getInstance().scan(new BleScanCallback() { @Override public void onScanStarted(boolean success) { } @Override public void onLeScan(BleDevice bleDevice) { } @Override public void onScanning(BleDevice bleDevice) { } @Override public void onScanFinished(List<BleDevice> scanResultList) { } });
onScanStarted(boolean success)
: 會(huì)回到主線程碰镜,參數(shù)表示本次掃描動(dòng)作是否開(kāi)啟成功。由于藍(lán)牙沒(méi)有打開(kāi)习瑰,上一次掃描沒(méi)有結(jié)束等原因绪颖,會(huì)造成掃描開(kāi)啟失敗。
onLeScan(BleDevice bleDevice)
:掃描過(guò)程中所有被掃描到的結(jié)果回調(diào)。由于掃描及過(guò)濾的過(guò)程是在工作線程中的柠横,此方法也處于工作線程中窃款。同一個(gè)設(shè)備會(huì)在不同的時(shí)間,攜帶自身不同的狀態(tài)(比如信號(hào)強(qiáng)度等)牍氛,出現(xiàn)在這個(gè)回調(diào)方法中晨继,出現(xiàn)次數(shù)取決于周圍的設(shè)備量及外圍設(shè)備的廣播間隔。
onScanning(BleDevice bleDevice)
:掃描過(guò)程中的所有過(guò)濾后的結(jié)果回調(diào)搬俊。與onLeScan區(qū)別之處在于:它會(huì)回到主線程紊扬;同一個(gè)設(shè)備只會(huì)出現(xiàn)一次;出現(xiàn)的設(shè)備是經(jīng)過(guò)掃描過(guò)濾規(guī)則過(guò)濾后的設(shè)備唉擂。
onScanFinished(List<BleDevice> scanResultList)
:本次掃描時(shí)段內(nèi)所有被掃描且過(guò)濾后的設(shè)備集合餐屎。它會(huì)回到主線程,相當(dāng)于onScanning設(shè)備之和玩祟。
1.4. 設(shè)備信息
掃描得到的BLE外圍設(shè)備腹缩,會(huì)以BleDevice
對(duì)象的形式,作為后續(xù)操作的最小單元對(duì)象空扎。它本身含有這些信息:
String getName()
:藍(lán)牙廣播名
String getMac()
:藍(lán)牙Mac地址
byte[] getScanRecord()
: 被掃描到時(shí)候攜帶的廣播數(shù)據(jù)
int getRssi()
:被掃描到時(shí)候的信號(hào)強(qiáng)度
后續(xù)進(jìn)行設(shè)備連接藏鹊、斷開(kāi)、判斷設(shè)備狀態(tài)勺卢,讀寫操作等時(shí)候伙判,都會(huì)用到這個(gè)對(duì)象象对『诔溃可以把它理解為外圍藍(lán)牙設(shè)備的載體,所有對(duì)外圍藍(lán)牙設(shè)備的操作勒魔,都通過(guò)這個(gè)對(duì)象來(lái)傳導(dǎo)甫煞。
1.5. 連接、斷連冠绢、監(jiān)控連接狀態(tài)
拿到設(shè)備對(duì)象之后抚吠,可以進(jìn)行連接操作。
BleManager.getInstance().connect(bleDevice, new BleGattCallback() {
@Override
public void onStartConnect() {
}
@Override
public void onConnectFail(BleException exception) {
}
@Override
public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {
}
@Override
public void onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status) {
}
});
onStartConnect()
:開(kāi)始進(jìn)行連接弟胀。
onConnectFail(BleException exception)
:連接不成功楷力。
onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status)
:連接成功并發(fā)現(xiàn)服務(wù)。
onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status)
:連接斷開(kāi)孵户,特指連接后再斷開(kāi)的情況萧朝。在這里可以監(jiān)控設(shè)備的連接狀態(tài),一旦連接斷開(kāi)夏哭,可以根據(jù)自身情況考慮對(duì)BleDevice對(duì)象進(jìn)行重連操作检柬。需要注意的是,斷開(kāi)和重連之間最好間隔一段時(shí)間竖配,否則可能會(huì)出現(xiàn)長(zhǎng)時(shí)間連接不上的情況何址。此外里逆,如果通過(guò)調(diào)用disconnect(BleDevice bleDevice)
方法,主動(dòng)斷開(kāi)藍(lán)牙連接的結(jié)果也會(huì)在這個(gè)方法中回調(diào)用爪,此時(shí)isActiveDisConnected將會(huì)是true原押。
1.6. GATT協(xié)議
BLE連接都是建立在 GATT (Generic Attribute Profile) 協(xié)議之上。GATT 是一個(gè)在藍(lán)牙連接之上的發(fā)送和接收很短的數(shù)據(jù)段的通用規(guī)范偎血,這些很短的數(shù)據(jù)段被稱為屬性(Attribute)班眯。它定義兩個(gè) BLE 設(shè)備通過(guò)Service 和 Characteristic 進(jìn)行通信。GATT 就是使用了 ATT(Attribute Protocol)協(xié)議烁巫,ATT 協(xié)議把 Service, Characteristic以及對(duì)應(yīng)的數(shù)據(jù)保存在一個(gè)查找表中署隘,次查找表使用 16 bit ID 作為每一項(xiàng)的索引。
關(guān)于GATT這部分內(nèi)容會(huì)在下面重點(diǎn)講解亚隙〈挪停總之,中心設(shè)備和外設(shè)需要雙向通信的話阿弃,唯一的方式就是建立 GATT 連接诊霹。當(dāng)連接成功之后,外圍設(shè)備與中心設(shè)備之間就建立起了GATT連接渣淳。
上面講到的connect(BleDevice bleDevice, BleGattCallback bleGattCallback)
方法其實(shí)是有返回值的脾还,這個(gè)返回值就是BluetoothGatt
。當(dāng)然還有其他方式可以獲取BluetoothGatt
對(duì)象入愧,連接成功后鄙漏,調(diào)用:
BluetoothGatt gatt = BleManager.getInstance().getBluetoothGatt(BleDevice bleDevice);
通過(guò)BluetoothGatt
對(duì)象作為連接橋梁棺蛛,中心設(shè)備可以獲取外圍設(shè)備的很多信息怔蚌,以及雙向通信。
首先旁赊,就可以獲取這個(gè)藍(lán)牙設(shè)備所擁有的Service和Characteristic桦踊。每一個(gè)屬性都可以被定義作不同的用途,通過(guò)它們來(lái)進(jìn)行協(xié)議通信终畅。下面的方法籍胯,就是通過(guò)BluetoothGatt
,查找出所有的Service和Characteristic的UUID:
List<BluetoothGattService> serviceList = bluetoothGatt.getServices();
for (BluetoothGattService service : serviceList) {
UUID uuid_service = service.getUuid();
List<BluetoothGattCharacteristic> characteristicList= service.getCharacteristics();
for(BluetoothGattCharacteristic characteristic : characteristicList) {
UUID uuid_chara = characteristic.getUuid();
}
}
1.7. 協(xié)議通信
APP與設(shè)備建立了連接离福,并且知道了Service和Characteristic(需要與硬件協(xié)議溝通確認(rèn))之后杖狼,我們就可以通過(guò)BLE協(xié)議進(jìn)行通信了。通信的橋梁术徊,主要就是是通過(guò) 標(biāo)準(zhǔn)的或者自定義的Characteristic本刽,中文我們稱之為“特征”。我們可以從 Characteristic 讀數(shù)據(jù)和寫數(shù)據(jù)。這樣就實(shí)現(xiàn)了雙向的通信子寓。站在APP作為中心設(shè)備的角度暗挑,常用于數(shù)據(jù)交互的通信方式主要有3種:接收通知、寫斜友、讀炸裆,此外還有設(shè)置最大傳輸單元,獲取實(shí)時(shí)信號(hào)強(qiáng)度等通信操作鲜屏。
-
接收通知
有兩種方式可以接收通知烹看,indicate和notify。indicate和notify的區(qū)別就在于洛史,indicate是一定會(huì)收到數(shù)據(jù)惯殊,notify有可能會(huì)丟失數(shù)據(jù)。indicate底層封裝了應(yīng)答機(jī)制也殖,如果沒(méi)有收到中央設(shè)備的回應(yīng)土思,會(huì)再次發(fā)送直至成功;而notify不會(huì)有central收到數(shù)據(jù)的回應(yīng)忆嗜,可能無(wú)法保證數(shù)據(jù)到達(dá)的準(zhǔn)確性己儒,優(yōu)勢(shì)是速度快。通常情況下捆毫,當(dāng)外圍設(shè)備需要不斷地發(fā)送數(shù)據(jù)給APP的時(shí)候闪湾,比如血壓計(jì)在測(cè)量過(guò)程中的壓力變化,胎心儀在監(jiān)護(hù)過(guò)程中的實(shí)時(shí)數(shù)據(jù)傳輸绩卤,這種頻繁的情況下途样,優(yōu)先考慮notify形式。當(dāng)只需要發(fā)送很少且很重要的一條數(shù)據(jù)給APP的時(shí)候省艳,優(yōu)先考慮indicate形式娘纷。當(dāng)然,從Android開(kāi)發(fā)角度的出發(fā)跋炕,如果硬件放已經(jīng)考慮了成熟的協(xié)議和發(fā)送方式,我們需要做的僅僅是根據(jù)其配置的數(shù)據(jù)發(fā)送方式進(jìn)行相應(yīng)的對(duì)接即可律适。
打開(kāi)notifyBleManager.getInstance().notify( bleDevice, uuid_service, uuid_characteristic_notify, new BleNotifyCallback() { @Override public void onNotifySuccess() { // 打開(kāi)通知操作成功 } @Override public void onNotifyFailure(BleException exception) { // 打開(kāi)通知操作失敗 } @Override public void onCharacteristicChanged(byte[] data) { // 打開(kāi)通知后辐烂,設(shè)備發(fā)過(guò)來(lái)的數(shù)據(jù)將在這里出現(xiàn) } });
關(guān)閉notify
BleManager.getInstance().stopNotify(uuid_service, uuid_characteristic_notify);
打開(kāi)indicate
BleManager.getInstance().indicate( bleDevice, uuid_service, uuid_characteristic_indicate, new BleIndicateCallback() { @Override public void onIndicateSuccess() { // 打開(kāi)通知操作成功 } @Override public void onIndicateFailure(BleException exception) { // 打開(kāi)通知操作失敗 } @Override public void onCharacteristicChanged(byte[] data) { // 打開(kāi)通知后,設(shè)備發(fā)過(guò)來(lái)的數(shù)據(jù)將在這里出現(xiàn) } });
關(guān)閉indicate
BleManager.getInstance().stopIndicate(uuid_service, uuid_characteristic_indicate);
這里的通知操作用到了兩個(gè)關(guān)鍵的參數(shù)捂贿,
uuid_service
和uuid_characteristic_notify
(或uuid_characteristic_indicate
)纠修,就是上面提到的Service和Characteristic,此處以字符串的形式體現(xiàn)厂僧,不區(qū)分大小寫扣草。 -
讀寫
BleManager.getInstance().read( bleDevice, uuid_service, uuid_characteristic_read, new BleReadCallback() { @Override public void onReadSuccess(byte[] data) { // 讀特征值數(shù)據(jù)成功 } @Override public void onReadFailure(BleException exception) { // 讀特征值數(shù)據(jù)失敗 } }); BleManager.getInstance().write( bleDevice, uuid_service, uuid_characteristic_write, data, new BleWriteCallback() { @Override public void onWriteSuccess(int current, int total, byte[] justWrite) { // 發(fā)送數(shù)據(jù)到設(shè)備成功(分包發(fā)送的情況下,可以通過(guò)方法中返回的參數(shù)可以查看發(fā)送進(jìn)度) } @Override public void onWriteFailure(BleException exception) { // 發(fā)送數(shù)據(jù)到設(shè)備失敗 } });
進(jìn)行BLE數(shù)據(jù)相互發(fā)送的時(shí)候,一次最多能發(fā)送20個(gè)字節(jié)辰妙。如果需要發(fā)送的數(shù)據(jù)超過(guò)20個(gè)字節(jié)鹰祸,有兩種方法,一種是主動(dòng)嘗試拓寬MTU密浑,另一種是采用分包傳輸?shù)姆绞酵苡ぁ?蚣苤械膚rite方法尔破,當(dāng)遇到數(shù)據(jù)超過(guò)20字節(jié)的情況時(shí)街图,默認(rèn)是進(jìn)行分包發(fā)送的。
-
設(shè)置最大傳輸單元MTU
BleManager.getInstance().setMtu(bleDevice, mtu, new BleMtuChangedCallback() { @Override public void onSetMTUFailure(BleException exception) { // 設(shè)置MTU失敗 } @Override public void onMtuChanged(int mtu) { // 設(shè)置MTU成功懒构,并獲得當(dāng)前設(shè)備傳輸支持的MTU值 } });
-
獲取設(shè)備的實(shí)時(shí)信號(hào)強(qiáng)度Rssi
BleManager.getInstance().readRssi( bleDevice, new BleRssiCallback() { @Override public void onRssiFailure(BleException exception) { // 讀取設(shè)備的信號(hào)強(qiáng)度失敗 } @Override public void onRssiSuccess(int rssi) { // 讀取設(shè)備的信號(hào)強(qiáng)度成功 } });
在BLE設(shè)備通信過(guò)程中餐济,有幾點(diǎn)經(jīng)驗(yàn)分享給大家:
- 兩次操作之間最好間隔一小段時(shí)間,如100ms(具體時(shí)間可以根據(jù)自己實(shí)際藍(lán)牙外設(shè)自行嘗試延長(zhǎng)或縮短)胆剧。舉例颤介,
onConnectSuccess
之后,延遲100ms再進(jìn)行notify
赞赖,之后再延遲100ms進(jìn)行write
滚朵。 - 連接及連接后的過(guò)程中,時(shí)刻關(guān)注onDisConnected方法前域,然后做處理辕近。
- 斷開(kāi)后如果需要重連,也請(qǐng)延遲一段時(shí)間匿垄,否則會(huì)造成阻塞移宅。
2. BLE開(kāi)發(fā)實(shí)踐方面的理解
在分解FastBle源碼之前,我首先介紹一下BLE通信一些理論知識(shí)椿疗。
2.1 藍(lán)牙簡(jiǎn)介
藍(lán)牙是一種近距離無(wú)線通信技術(shù)漏峰。它的特性就是近距離通信,典型距離是 10 米以內(nèi)届榄,傳輸速度最高可達(dá) 24 Mbps浅乔,支持多連接,安全性高铝条,非常適合用智能設(shè)備上靖苇。
2.2 藍(lán)牙技術(shù)的版本演進(jìn)
- 1999年發(fā)布1.0版本,目前市面上已很少見(jiàn)到班缰;
- 2002年發(fā)布1.1版本贤壁,目前市面上已很少見(jiàn)到;
- 2004年發(fā)布2.0版本埠忘,目前市面上已很少見(jiàn)到脾拆;
- 2007年發(fā)布的2.1版本馒索,是之前使用最廣的,也是我們所謂的經(jīng)典藍(lán)牙名船。
- 2009年推出藍(lán)牙 3.0版本绰上,也就是所謂的高速藍(lán)牙,傳輸速率理論上可高達(dá)24 Mbit/s包帚;
- 2010年推出藍(lán)牙4.0版本渔期,它是相對(duì)之前版本的集大成者,它包括經(jīng)典藍(lán)牙渴邦、高速藍(lán)牙和藍(lán)牙低功耗協(xié)議疯趟。經(jīng)典藍(lán)牙包括舊有藍(lán)牙協(xié)議,高速藍(lán)牙基于Wi-Fi谋梭,低功耗藍(lán)牙就是BLE信峻。
- 2016年藍(lán)牙技術(shù)聯(lián)盟提出了新的藍(lán)牙技術(shù)標(biāo)準(zhǔn),即藍(lán)牙5.0版本瓮床。藍(lán)牙5.0針對(duì)低功耗設(shè)備速度有相應(yīng)提升和優(yōu)化盹舞,結(jié)合wifi對(duì)室內(nèi)位置進(jìn)行輔助定位,提高傳輸速度隘庄,增加有效工作距離踢步,主要是針對(duì)物聯(lián)網(wǎng)方向的改進(jìn)。
2.3 Android上BLE功能的逐步演進(jìn)
在Android開(kāi)發(fā)過(guò)程中丑掺,版本的碎片化一直是需要考慮的問(wèn)題获印,再加上廠商定制及藍(lán)牙本身也和Android一樣一直在發(fā)展過(guò)程中,所以對(duì)于每一個(gè)版本支持什么功能街州,是我們需要知道的兼丰。
- Android 4.3 開(kāi)始,開(kāi)始支持BLE功能唆缴,但只支持Central Mode(中心模式)
- Android 5.0開(kāi)始鳍征,開(kāi)始支持Peripheral Mode(外設(shè)模式)
中心模式和外設(shè)模式是什么意思?
- Central Mode: Android端作為中心設(shè)備面徽,連接其他外圍設(shè)備艳丛。
- Peripheral Mode:Android端作為外圍設(shè)備,被其他中心設(shè)備連接斗忌。在Android 5.0支持外設(shè)模式之后质礼,才算實(shí)現(xiàn)了兩臺(tái)Android手機(jī)通過(guò)BLE進(jìn)行相互通信。
2.4 藍(lán)牙的廣播和掃描
以下內(nèi)容部分參考自BLE Introduction 织阳。
關(guān)于這部分內(nèi)容,需要引入一個(gè)概念砰粹,GAP(Generic Access Profile)唧躲,它用來(lái)控制設(shè)備連接和廣播造挽。GAP 使你的設(shè)備被其他設(shè)備可見(jiàn),并決定了你的設(shè)備是否可以或者怎樣與設(shè)備進(jìn)行交互弄痹。例如 Beacon 設(shè)備就只是向外發(fā)送廣播饭入,不支持連接;小米手環(huán)就可以與中心設(shè)備建立連接肛真。
在 GAP 中藍(lán)牙設(shè)備可以向外廣播數(shù)據(jù)包谐丢,廣播包分為兩部分: Advertising Data Payload(廣播數(shù)據(jù))和 Scan Response Data Payload(掃描回復(fù)),每種數(shù)據(jù)最長(zhǎng)可以包含 31 byte蚓让。這里廣播數(shù)據(jù)是必需的乾忱,因?yàn)橥庠O(shè)必需不停的向外廣播,讓中心設(shè)備知道它的存在历极。掃描回復(fù)是可選的窄瘟,中心設(shè)備可以向外設(shè)請(qǐng)求掃描回復(fù),這里包含一些設(shè)備額外的信息趟卸,例如設(shè)備的名字蹄葱。在 Android 中,系統(tǒng)會(huì)把這兩個(gè)數(shù)據(jù)拼接在一起锄列,返回一個(gè) 62 字節(jié)的數(shù)組图云。這些廣播數(shù)據(jù)可以自己手動(dòng)去解析,在 Android 5.0 也提供 ScanRecord 幫你解析邻邮,直接可以通過(guò)這個(gè)類獲得有意義的數(shù)據(jù)竣况。廣播中可以有哪些數(shù)據(jù)類型呢?設(shè)備連接屬性饶囚,標(biāo)識(shí)設(shè)備支持的 BLE 模式政恍,這個(gè)是必須的。設(shè)備名字绘梦,設(shè)備包含的關(guān)鍵 GATT service材蹬,或者 Service data,廠商自定義數(shù)據(jù)等等规惰。
外圍設(shè)備會(huì)設(shè)定一個(gè)廣播間隔睬塌,每個(gè)廣播間隔中,它會(huì)重新發(fā)送自己的廣播數(shù)據(jù)歇万。廣播間隔越長(zhǎng)揩晴,越省電,同時(shí)也不太容易掃描到贪磺。
剛剛講到硫兰,GAP決定了你的設(shè)備怎樣與其他設(shè)備進(jìn)行交互。答案是有2種方式:
-
完全基于廣播的方式
也有些情況是不需要連接的寒锚,只要外設(shè)廣播自己的數(shù)據(jù)即可劫映。用這種方式主要目的是讓外圍設(shè)備违孝,把自己的信息發(fā)送給多個(gè)中心設(shè)備。使用廣播這種方式最典型的應(yīng)用就是蘋果的 iBeacon泳赋。這是蘋果公司定義的基于 BLE 廣播實(shí)現(xiàn)的功能雌桑,可以實(shí)現(xiàn)廣告推送和室內(nèi)定位。這也說(shuō)明了祖今,APP 使用 BLE校坑,需要定位權(quán)限。基于非連接的千诬,這種應(yīng)用就是依賴 BLE 的廣播耍目,也叫作 Beacon。這里有兩個(gè)角色大渤,發(fā)送廣播的一方叫做 Broadcaster制妄,監(jiān)聽(tīng)廣播的一方叫 Observer。
-
基于GATT連接的方式
大部分情況下泵三,外設(shè)通過(guò)廣播自己來(lái)讓中心設(shè)備發(fā)現(xiàn)自己耕捞,并建立 GATT 連接,從而進(jìn)行更多的數(shù)據(jù)交換烫幕。這里有且僅有兩個(gè)角色俺抽,發(fā)起連接的一方,叫做中心設(shè)備—Central较曼,被連接的設(shè)備磷斧,叫做外設(shè)—Peripheral。- 外圍設(shè)備:這一般就是非常小或者簡(jiǎn)單的低功耗設(shè)備捷犹,用來(lái)提供數(shù)據(jù)弛饭,并連接到一個(gè)更加相對(duì)強(qiáng)大的中心設(shè)備,例如小米手環(huán)萍歉。
- 中心設(shè)備:中心設(shè)備相對(duì)比較強(qiáng)大侣颂,用來(lái)連接其他外圍設(shè)備,例如手機(jī)等枪孩。
GATT 連接需要特別注意的是:GATT 連接是獨(dú)占的憔晒。也就是一個(gè) BLE 外設(shè)同時(shí)只能被一個(gè)中心設(shè)備連接。一旦外設(shè)被連接蔑舞,它就會(huì)馬上停止廣播拒担,這樣它就對(duì)其他設(shè)備不可見(jiàn)了。當(dāng)設(shè)備斷開(kāi)攻询,它又開(kāi)始廣播从撼。中心設(shè)備和外設(shè)需要雙向通信的話,唯一的方式就是建立 GATT 連接钧栖。
GATT 通信的雙方是 C/S 關(guān)系谋逻。外設(shè)作為 GATT 服務(wù)端(Server)呆馁,它維持了 ATT 的查找表以及 service 和 characteristic 的定義桐经。中心設(shè)備是 GATT 客戶端(Client)毁兆,它向 Server 發(fā)起請(qǐng)求。需要注意的是阴挣,所有的通信事件气堕,都是由客戶端發(fā)起,并且接收服務(wù)端的響應(yīng)畔咧。
2.5 BLE通信基礎(chǔ)
BLE通信的基礎(chǔ)有兩個(gè)重要的概念茎芭,ATT和GATT。
ATT
全稱 attribute protocol誓沸,中文名“屬性協(xié)議”梅桩。它是 BLE 通信的基礎(chǔ)。ATT 把數(shù)據(jù)封裝拜隧,向外暴露為“屬性”宿百,提供“屬性”的為服務(wù)端,獲取“屬性”的為客戶端洪添。ATT 是專門為低功耗藍(lán)牙設(shè)計(jì)的垦页,結(jié)構(gòu)非常簡(jiǎn)單,數(shù)據(jù)長(zhǎng)度很短干奢。GATT
全稱 Generic Attribute Profile痊焊, 中文名“通用屬性配置文件”。它是在ATT 的基礎(chǔ)上忿峻,對(duì) ATT 進(jìn)行的進(jìn)一步邏輯封裝薄啥,定義數(shù)據(jù)的交互方式和含義。GATT是我們做 BLE 開(kāi)發(fā)的時(shí)候直接接觸的概念逛尚。-
GATT 層級(jí)
GATT按照層級(jí)定義了4個(gè)概念:配置文件(Profile)垄惧、服務(wù)(Service)、特征(Characteristic)和描述(Descriptor)黑低。他們的關(guān)系是這樣的:Profile 就是定義了一個(gè)實(shí)際的應(yīng)用場(chǎng)景赘艳,一個(gè) Profile包含若干個(gè) Service,一個(gè) Service 包含若干個(gè) Characteristic克握,一個(gè) Characteristic 可以包含若干 Descriptor蕾管。
GATT層級(jí) Profile
Profile 并不是實(shí)際存在于 BLE 外設(shè)上的,它只是一個(gè)被 Bluetooth SIG 或者外設(shè)設(shè)計(jì)者預(yù)先定義的 Service 的集合菩暗。例如心率Profile(Heart Rate Profile)就是結(jié)合了 Heart Rate Service 和 Device Information Service掰曾。所有官方通過(guò) GATT Profile 的列表可以從這里找到。Service
Service 是把數(shù)據(jù)分成一個(gè)個(gè)的獨(dú)立邏輯項(xiàng)停团,它包含一個(gè)或者多個(gè) Characteristic旷坦。每個(gè) Service 有一個(gè) UUID 唯一標(biāo)識(shí)掏熬。 UUID 有 16 bit 的,或者 128 bit 的秒梅。16 bit 的 UUID 是官方通過(guò)認(rèn)證的旗芬,需要花錢購(gòu)買,128 bit 是自定義的捆蜀,這個(gè)就可以自己隨便設(shè)置疮丛。官方通過(guò)了一些標(biāo)準(zhǔn) Service,完整列表在這里辆它。以 Heart Rate Service為例誊薄,可以看到它的官方通過(guò) 16 bit UUID 是0x180D
,包含 3 個(gè) Characteristic:Heart Rate Measurement, Body Sensor Location 和 Heart Rate Control Point锰茉,并且定義了只有第一個(gè)是必須的呢蔫,它是可選實(shí)現(xiàn)的。Characteristic
需要重點(diǎn)提一下Characteristic飒筑, 它定義了數(shù)值和操作片吊,包含一個(gè)Characteristic聲明、Characteristic屬性扬霜、值定鸟、值的描述(Optional)。通常我們講的 BLE 通信著瓶,其實(shí)就是對(duì) Characteristic 的讀寫或者訂閱通知联予。比如在實(shí)際操作過(guò)程中,我對(duì)某一個(gè)Characteristic進(jìn)行讀材原,就是獲取這個(gè)Characteristic的value沸久。-
UUID
Service、Characteristic 和 Descriptor 都是使用 UUID 唯一標(biāo)示的余蟹。UUID 是全局唯一標(biāo)識(shí)卷胯,它是 128bit 的值,為了便于識(shí)別和閱讀威酒,一般以 “8位-4位-4位-4位-12位”的16進(jìn)制標(biāo)示窑睁,比如“12345678-abcd-1000-8000-123456000000”。
但是葵孤,128bit的UUID 太長(zhǎng)担钮,考慮到在低功耗藍(lán)牙中,數(shù)據(jù)長(zhǎng)度非常受限的情況尤仍,藍(lán)牙又使用了所謂的 16 bit 或者 32 bit 的 UUID箫津,形式如下:“0000XXXX-0000-1000-8000-00805F9B34FB”。除了 “XXXX” 那幾位以外,其他都是固定苏遥,所以說(shuō)饼拍,其實(shí) 16 bit UUID 是對(duì)應(yīng)了一個(gè) 128 bit 的 UUID。這樣一來(lái)田炭,UUID 就大幅減少了师抄,例如 16 bit UUID只有有限的 65536(16的四次方) 個(gè)。與此同時(shí)诫肠,因?yàn)閿?shù)量有限司澎,所以 16 bit UUID 并不能隨便使用。藍(lán)牙技術(shù)聯(lián)盟已經(jīng)預(yù)先定義了一些 UUID栋豫,我們可以直接使用,比如“00001011-0000-1000-8000-00805F9B34FB”就一個(gè)是常見(jiàn)于BLE設(shè)備中的UUID谚殊。當(dāng)然也可以花錢定制自定義的UUID丧鸯。
3. FastBle源碼解析
通過(guò)上面BLE的基礎(chǔ)理論,我們可以分析到嫩絮,BLE通信實(shí)際上就是先由客戶端發(fā)起與服務(wù)端的連接丛肢,再通過(guò)服務(wù)端的找到其Characteristic進(jìn)行兩者間的數(shù)據(jù)交互。
在FastBle源碼中剿干,首先看BleManager
中的connect()
方法:
public BluetoothGatt connect(BleDevice bleDevice, BleGattCallback bleGattCallback) {
if (bleGattCallback == null) {
throw new IllegalArgumentException("BleGattCallback can not be Null!");
}
if (!isBlueEnable()) {
BleLog.e("Bluetooth not enable!");
bleGattCallback.onConnectFail(new OtherException("Bluetooth not enable!"));
return null;
}
if (Looper.myLooper() == null || Looper.myLooper() != Looper.getMainLooper()) {
BleLog.w("Be careful: currentThread is not MainThread!");
}
if (bleDevice == null || bleDevice.getDevice() == null) {
bleGattCallback.onConnectFail(new OtherException("Not Found Device Exception Occurred!"));
} else {
BleBluetooth bleBluetooth = new BleBluetooth(bleDevice);
boolean autoConnect = bleScanRuleConfig.isAutoConnect();
return bleBluetooth.connect(bleDevice, autoConnect, bleGattCallback);
}
return null;
}
這個(gè)方法將掃描到的外圍設(shè)備對(duì)象傳入蜂怎,通過(guò)一些必要的條件判斷之后,調(diào)用bleBluetooth.connect()
進(jìn)行連接置尔。我們?nèi)タ匆幌?code>BleBluetooth這個(gè)類:
public BleBluetooth(BleDevice bleDevice) {
this.bleDevice = bleDevice;
}
上面的BleBluetooth
的構(gòu)造方法是傳入一個(gè)藍(lán)牙設(shè)備對(duì)象杠步。由此可見(jiàn),一個(gè)BleBluetooth
可能代表你的Android與這一個(gè)外圍設(shè)備整個(gè)交互過(guò)程榜轿,從開(kāi)始連接幽歼,到中間數(shù)據(jù)交互,一直到斷開(kāi)連接的整個(gè)過(guò)程谬盐。在多連接情況下甸私,有多少外圍設(shè)備,設(shè)備池中就維護(hù)著多少個(gè)BleBluetooth
對(duì)象飞傀。
MultipleBluetoothController
就是控制多設(shè)備連接的皇型。它里面有增加和移除設(shè)備的方法,如下圖的addBleBluetooth
和removeBleBluetooth
砸烦,傳入的參數(shù)就是BleBluetooth
對(duì)象弃鸦,驗(yàn)證了上面的說(shuō)法。
public synchronized void addBleBluetooth(BleBluetooth bleBluetooth) {
if (bleBluetooth == null) {
return;
}
if (!bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {
bleLruHashMap.put(bleBluetooth.getDeviceKey(), bleBluetooth);
}
}
public synchronized void removeBleBluetooth(BleBluetooth bleBluetooth) {
if (bleBluetooth == null) {
return;
}
if (bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {
bleLruHashMap.remove(bleBluetooth.getDeviceKey());
}
}
回到BleBlutooth
的connect
方法:
public synchronized BluetoothGatt connect(BleDevice bleDevice,
boolean autoConnect,
BleGattCallback callback) {
addConnectGattCallback(callback);
isMainThread = Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper();
BluetoothGatt gatt;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
autoConnect, coreGattCallback, TRANSPORT_LE);
} else {
gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
autoConnect, coreGattCallback);
}
if (gatt != null) {
if (bleGattCallback != null)
bleGattCallback.onStartConnect();
connectState = BleConnectState.CONNECT_CONNECTING;
}
return gatt;
}
可見(jiàn)外冀,最終也是調(diào)用了原生API中的BluetoothDevice
的connectGatt()
方法寡键。在藍(lán)牙原理分析中講到,連接過(guò)程中要?jiǎng)?chuàng)建一個(gè)BluetoothGattCallback
,用來(lái)作為回調(diào)西轩,這個(gè)類非常重要员舵,所有的 GATT 操作的回調(diào)都在這里。而此處的coreGattCallback
應(yīng)該就扮演著這個(gè)角色藕畔,它是BluetoothGattCallback
的實(shí)現(xiàn)類對(duì)象马僻,對(duì)操作回調(diào)結(jié)果做了封裝和分發(fā)。
private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothGatt.STATE_CONNECTED) {
gatt.discoverServices();
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
closeBluetoothGatt();
BleManager.getInstance().getMultipleBluetoothController().removeBleBluetooth(BleBluetooth.this);
if (connectState == BleConnectState.CONNECT_CONNECTING) {
connectState = BleConnectState.CONNECT_FAILURE;
if (isMainThread) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CONNECT_FAIL;
message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);
handler.sendMessage(message);
} else {
if (bleGattCallback != null)
bleGattCallback.onConnectFail(new ConnectException(gatt, status));
}
} else if (connectState == BleConnectState.CONNECT_CONNECTED) {
connectState = BleConnectState.CONNECT_DISCONNECT;
if (isMainThread) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_DISCONNECTED;
BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);
para.setAcitive(isActiveDisconnect);
para.setBleDevice(getDevice());
message.obj = para;
handler.sendMessage(message);
} else {
if (bleGattCallback != null)
bleGattCallback.onDisConnected(isActiveDisconnect, bleDevice, gatt, status);
}
}
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
BleLog.i("BluetoothGattCallback:onServicesDiscovered "
+ '\n' + "status: " + status
+ '\n' + "currentThread: " + Thread.currentThread().getId());
if (status == BluetoothGatt.GATT_SUCCESS) {
bluetoothGatt = gatt;
connectState = BleConnectState.CONNECT_CONNECTED;
isActiveDisconnect = false;
BleManager.getInstance().getMultipleBluetoothController().addBleBluetooth(BleBluetooth.this);
if (isMainThread) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CONNECT_SUCCESS;
BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);
para.setBleDevice(getDevice());
message.obj = para;
handler.sendMessage(message);
} else {
if (bleGattCallback != null)
bleGattCallback.onConnectSuccess(getDevice(), gatt, status);
}
} else {
closeBluetoothGatt();
connectState = BleConnectState.CONNECT_FAILURE;
if (isMainThread) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CONNECT_FAIL;
message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);
handler.sendMessage(message);
} else {
if (bleGattCallback != null)
bleGattCallback.onConnectFail(new ConnectException(gatt, status));
}
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleNotifyCallback) {
BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;
if (characteristic.getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {
Handler handler = bleNotifyCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE;
message.obj = bleNotifyCallback;
Bundle bundle = new Bundle();
bundle.putByteArray(BleMsg.KEY_NOTIFY_BUNDLE_VALUE, characteristic.getValue());
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
iterator = bleIndicateCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleIndicateCallback) {
BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;
if (characteristic.getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {
Handler handler = bleIndicateCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_INDICATE_DATA_CHANGE;
message.obj = bleIndicateCallback;
Bundle bundle = new Bundle();
bundle.putByteArray(BleMsg.KEY_INDICATE_BUNDLE_VALUE, characteristic.getValue());
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleNotifyCallback) {
BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;
if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {
Handler handler = bleNotifyCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_NOTIFY_RESULT;
message.obj = bleNotifyCallback;
Bundle bundle = new Bundle();
bundle.putInt(BleMsg.KEY_NOTIFY_BUNDLE_STATUS, status);
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
iterator = bleIndicateCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleIndicateCallback) {
BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;
if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {
Handler handler = bleIndicateCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_INDICATE_RESULT;
message.obj = bleIndicateCallback;
Bundle bundle = new Bundle();
bundle.putInt(BleMsg.KEY_INDICATE_BUNDLE_STATUS, status);
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
Iterator iterator = bleWriteCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleWriteCallback) {
BleWriteCallback bleWriteCallback = (BleWriteCallback) callback;
if (characteristic.getUuid().toString().equalsIgnoreCase(bleWriteCallback.getKey())) {
Handler handler = bleWriteCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_WRITE_RESULT;
message.obj = bleWriteCallback;
Bundle bundle = new Bundle();
bundle.putInt(BleMsg.KEY_WRITE_BUNDLE_STATUS, status);
bundle.putByteArray(BleMsg.KEY_WRITE_BUNDLE_VALUE, characteristic.getValue());
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
Iterator iterator = bleReadCallbackHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object callback = entry.getValue();
if (callback instanceof BleReadCallback) {
BleReadCallback bleReadCallback = (BleReadCallback) callback;
if (characteristic.getUuid().toString().equalsIgnoreCase(bleReadCallback.getKey())) {
Handler handler = bleReadCallback.getHandler();
if (handler != null) {
Message message = handler.obtainMessage();
message.what = BleMsg.MSG_CHA_READ_RESULT;
message.obj = bleReadCallback;
Bundle bundle = new Bundle();
bundle.putInt(BleMsg.KEY_READ_BUNDLE_STATUS, status);
bundle.putByteArray(BleMsg.KEY_READ_BUNDLE_VALUE, characteristic.getValue());
message.setData(bundle);
handler.sendMessage(message);
}
}
}
}
}
};
在收到連接狀態(tài)注服、讀韭邓、寫、通知等操作的結(jié)果回調(diào)之后溶弟,通過(guò)消息隊(duì)列機(jī)制女淑,交由相應(yīng)的Handler去處理。那處理消息的Handler在哪里辜御?舉例其中的write操作Handler handler = bleWriteCallback.getHandler();
鸭你,handler對(duì)象被包含在了這個(gè)write操作的callback中。
public abstract class BleBaseCallback {
private String key;
private Handler handler;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Handler getHandler() {
return handler;
}
public void setHandler(Handler handler) {
this.handler = handler;
}
}
所有的操作的callback都繼承自這個(gè)BleBaseCallback
抽象類擒权,它有兩個(gè)成員變量袱巨。一個(gè)key
,標(biāo)識(shí)著這個(gè)callback歸屬于哪一個(gè)Characteristic
的操作碳抄;另一個(gè)handler
愉老,用于傳遞底層發(fā)來(lái)的操作結(jié)果,最終將結(jié)果交由callback去拋給調(diào)用者剖效,完成一次接口回調(diào)嫉入。
private void handleCharacteristicWriteCallback(BleWriteCallback bleWriteCallback,
String uuid_write) {
if (bleWriteCallback != null) {
writeMsgInit();
bleWriteCallback.setKey(uuid_write);
bleWriteCallback.setHandler(mHandler);
mBleBluetooth.addWriteCallback(uuid_write, bleWriteCallback);
mHandler.sendMessageDelayed(
mHandler.obtainMessage(BleMsg.MSG_CHA_WRITE_START, bleWriteCallback),
BleManager.getInstance().getOperateTimeout());
}
}
上面這段源碼解釋了這個(gè)機(jī)制,每一次write操作之后贱鄙,都會(huì)對(duì)傳入的callback進(jìn)行唯一性標(biāo)記劝贸,再通過(guò)handler用來(lái)傳遞操作結(jié)果,同時(shí)將這個(gè)callback加入這個(gè)設(shè)備的BleBlutooth
對(duì)象的callback池中管理逗宁。
這樣就形成了APP維持一個(gè)設(shè)備連接池映九,一個(gè)設(shè)備連接池管理多個(gè)設(shè)備管理者,一個(gè)設(shè)備管理者管理多個(gè)不同類別的callback集合瞎颗,一個(gè)callback集合中含有多個(gè)同類的不同特征的callback件甥。