1、簡介


BLE是Bluetooth Low Energy的縮寫,又叫藍牙4.0,區(qū)別于藍牙3.0和之前的技術(shù)
主要特點是快速搜索,快速連接,超低功耗保持連接和數(shù)據(jù)傳輸,缺點:數(shù)據(jù)傳輸速率低,由于其具有低功耗特點,所以經(jīng)常用在可穿戴設(shè)備之中
BLE設(shè)備分單模和雙模兩種,雙模簡稱BR,商標為Bluetooth Smart Ready,單模簡稱BLE或者LE,商標為Bluetooth Smart,雙模兼容傳統(tǒng)藍牙,可以和傳統(tǒng)藍牙通信,也可以和BLE通信,單模只能和BR和單模的設(shè)備通信,不能和傳統(tǒng)藍牙通信,由于功耗低,待機長,所以常用在手環(huán)的智能設(shè)備上
Android是在4.3后才支持BLE,這說明不是所有藍牙手機都支持BLE,而且支持BLE的藍牙手機一般是雙模的
2、連接流程
- 1、獲取BluetoothManager對象或者是通過BluetoothAdapter的對象,兩個差多不
(BluetoothManager) getContext().getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter adapter = getBluetoothManager().getAdapter();
BluetoothAdapter.getDefaultAdapter()
藍牙是夸進程通信的,所以源碼使用了aidl來解決跨進程通信
public static synchronized BluetoothAdapter getDefaultAdapter() {
if (sAdapter == null) {
IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);
if (b != null) {
IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);
sAdapter = new BluetoothAdapter(managerService);
} else {
Log.e(TAG, "Bluetooth binder is null");
}
}
return sAdapter;
}
- 2、開始掃描BLE設(shè)備,兩種方式
1.使用 adapter.startLeScan(mScanCallback) 這個方法不建議使用,回調(diào)在ui線程,不要做耗時操作
adapter.startLeScan(mScanCallback)
private final BluetoothAdapter.LeScanCallback mScanCallback = new BluetoothAdapter.LeScanCallback() {
/**
* ui線程,不要做耗時操作
* @param device
* @param rssi 信號強度
* @param scanRecord 廣播數(shù)據(jù),解析廣播
*/
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
ScanResult result = new ScanResult(device, rssi, scanRecord);
if (TextUtils.isEmpty(device.getName())){
}else if (!mScanResults.containsKey(device.getAddress())) {
mScanResults.put(device.getAddress(), result);
Log.v(TAG, String.format("onLeScan: mac = %s, name = %s, record = (%s)%d",
device.getAddress(), device.getName(), byteToString(scanRecord), scanRecord.length));
mAdapter.refresh(mScanResults);
}
}
};
2、通過BluetoothLeScanner,目前項目使用這個
BluetoothAdapter adapter = getBluetoothManager().getAdapter();
BluetoothLeScanner bluetoothLeScanner = adapter.getBluetoothLeScanner();
bluetoothLeScanner.startScan(new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
});
3、 BluetoothAdapter.LeScanCallback回調(diào),獲取到了 BluetoothDevice 了,然后在獲取到GATT句柄
4、獲取到GATT句柄 BluetoothGatt,回調(diào)都在binder線程池,不能刷新ui,回調(diào)在哪個線程都不確定,建議所有的回調(diào)post到統(tǒng)一的線程,因為多線程同步有點問題哦,難搞的很
mBluetoothGatt = mDevice.connectGatt(this, false, new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.v(TAG, String.format("onConnectionStateChange: status = %d, newState = %d", status, newState));
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) {
onConnected();
if (!mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)) {
disconnect();
}
if (!mBluetoothGatt.discoverServices()) {
disconnect();
}
} else {
disconnect();
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.v(TAG, String.format("onServicesDiscovered: status = %d", status));
if (status == BluetoothGatt.GATT_SUCCESS) {
refreshProfile();
startChannel();
BluetoothUtils.setCharacteristicNotification(gatt, UUID_MYSERVICE, UUID_PACKET, true);
mBtnPacket.postDelayed(new Runnable() {
@Override
public void run() {
mBtnPacket.setEnabled(true);
}
}, 1000);
} else {
disconnect();
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.v(TAG, String.format("onCharacteristicRead: service = %s, character = %s, value = %s, status = %d",
characteristic.getService().getUuid(),
characteristic.getUuid(),
ByteUtils.byteToString(characteristic.getValue()),
status));
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.v(TAG, String.format("onCharacteristicWrite service = %s, character = %s, status = %d",
characteristic.getService().getUuid(),
characteristic.getUuid(),
status));
if (mChannelCallback != null) {
mChannelCallback.onCallback(status == BluetoothGatt.GATT_SUCCESS ? Code.SUCCESS : Code.FAIL);
mChannelCallback = null;
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.v(TAG, String.format("onCharacteristicChanged service = %s, character = %s, value = %s",
characteristic.getService().getUuid(),
characteristic.getUuid(),
ByteUtils.byteToString(characteristic.getValue())));
mChannel.onRead(characteristic.getValue());
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
Log.v(TAG, String.format("onReadRemoteRssi rssi = %d, status = %d", rssi, status));
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
Log.v(TAG, String.format("onDescriptorWrite service = %s, character = %s, descriptor = %s, status = %d",
descriptor.getCharacteristic().getService().getUuid(),
descriptor.getCharacteristic().getUuid(),
descriptor.getUuid(), status));
}
});
-
log可以看出這里是在binder線程池,每次連接都是一個獨立的對象,也在一個獨立的線程中,所以每次斷開連接都要把GATT關(guān)閉掉,而不是 disconnect();
image.png - 通過gatt= mDevice.connectGatt(this, false, new BluetoothGattCallback()出來的對象,其實是和
public void onServicesDiscovered(BluetoothGatt gatt, int status)是同一個對象,這個很有意思?具體我也不清楚為啥

- 注意事項
- GATT重連,BluetoothGatt 調(diào)用connet函數(shù),但是不建議這樣做,而是重新開啟一個gatt, 而不是用舊的gatt重連,mBluetoothGatt.connect(); 就是不用使用內(nèi)部的方法區(qū)進行重連?why?因為這里面有好多的坑
- 斷開連接 mBluetoothGatt.close(); 或者使用 disconnect();其實最好是使用close這個方法,這是穩(wěn)定性最好的辦法, disconnect(); 有些很奇怪的問題,能力不夠,不要去采坑,close后,就不收到回調(diào)了,不用等待回調(diào)
- BluetoothGattCallback接口回調(diào):onCharacteristicChanged 回調(diào)中讀取藍牙設(shè)備給我們發(fā)送的數(shù)據(jù)
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.v(TAG, String.format("onCharacteristicChanged service = %s, character = %s, value = %s",
characteristic.getService().getUuid(),
characteristic.getUuid(),
ByteUtils.byteToString(characteristic.getValue())));
mChannel.onRead(characteristic.getValue());
}
- 通過 BluetoothGatt 類并且通過uuid獲取到 BluetoothGattService ,在獲取到BluetoothGattCharacteristic ,通過這個對象去發(fā)送數(shù)據(jù), UUID 后面解釋
private BluetoothGattCharacteristic getNotifyCharacteristic() {
if (mBluetoothGatt != null) {
BluetoothGattService service = mBluetoothGatt.getService(UUID_MYSERVICE);
return service != null ? service.getCharacteristic(UUID_PACKET) : null;
}
return null;
}
BluetoothGattCharacteristic characteristic = getNotifyCharacteristic();
characteristic.setValue(bytes);
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
mBluetoothGatt.writeCharacteristic(characteristic);
mChannelCallback = callback;
3、什么是GATT
簡介:
GATT全稱Generic Attribute Profile,中文名叫通用屬性協(xié)議,它定義了services和characteristic兩種東西來完成低功耗藍牙設(shè)備之間的數(shù)據(jù)傳輸,它是建立在通用數(shù)據(jù)協(xié)議Attribute Protocol (ATT),之上的,ATT把services和characteristic以及相關(guān)的數(shù)據(jù)保存在一張簡單的查找表中,該表使用16-bit的id作為索引
一旦兩個設(shè)備建立了連接,GATT就開始發(fā)揮作用
GATT連接是獨占的,也就意味著一個BLE周邊設(shè)備同時只能與一個中心設(shè)備連接,一旦周邊設(shè)備與中心設(shè)備連接成功,直至連接斷開,它不再對外廣播自己的存在,其他的設(shè)備就無法發(fā)現(xiàn)該周邊設(shè)備的存在了。
周邊設(shè)備和中心設(shè)備要完成雙方的通信只能通過建立GATT連接的方式。
-
GATT架構(gòu)圖
image.png 1、profile可以理解為一種規(guī)范,一個標準的通信協(xié)議,其存在于手機中,藍牙組織規(guī)定一些標準的profile:HID OVER GATT ,防丟器等,每個profile中包含了多個service了。藍牙規(guī)范(Profile)是指藍牙通信在那一種用途下應(yīng)該使用的通信協(xié)議和相關(guān)的規(guī)范。藍牙1.1定義的profile有13個。SIG認為藍牙設(shè)備有4個最基本的Profile
2、service可以理解為一個服務(wù),在ble從機中,通過有多個服務(wù),例如電量信息服務(wù)、系統(tǒng)信息服務(wù)等,每個service中又包含多個characteristic特征值。每個具體的characteristic特征值才是ble通信的主題。比如當前的電量是80%,所以會通過電量的characteristic特征值存在從機的profile里,這樣主機就可以通過這個characteristic來讀取80%這個數(shù)據(jù)
3、characteristic特征值,ble主從機的通信均是通過characteristic來實現(xiàn),可以理解為一個標簽,通過這個標簽可以獲取或者寫入想要的內(nèi)容。
把Profile 當做是國家,Service 就是中國足球。Characteristic就是國足的某一個人,我需要找到這個人告訴他,明天不要提假球(類似對ble設(shè)備設(shè)置信息),所以需要uuid,uuid一般是問詢問硬件工程師,問他要這個設(shè)備的UUID
- 主要用的是service UUID 和characteristic UUID一般讀,寫和通知的UUID 就是 characteristic UUID
4、UUID到底是個啥?
- 通用唯一識別碼(英語:Universally Unique Identifier,縮寫:UUID)是用于計算機體系中以識別信息數(shù)目的一個128位標識符,還有相關(guān)的術(shù)語:全局唯一標識符
- UUID是由一組32位數(shù)的16進制數(shù)字所構(gòu)成,故UUID理論上的總數(shù)為1632=2128,約等于3.4 x 1038。也就是說若每納秒(ns)產(chǎn)生1兆個UUID,要花100億年才會將所有UUID用完
- UUID 的 16 個 8 位字節(jié)表示為 32 個十六進制(基數(shù)16)數(shù)字,顯示在由連字符分隔 '-' 的五個組中,"8-4-4-4-12" 總共 36 個字符(32 個字母數(shù)字字符和 4 個連字符)。例如:123e4567-e89b-12d3-a456-426655440000
5、BLE一些問題的總結(jié),剛?cè)肟硬痪?,持續(xù)記錄
- 經(jīng)典藍牙是通過socket ,BLE是通過gatt
- BLE分Service /Characteristic 。一個地址,Service相當于省,Characteristic相當于市
- 我覺得未來物聯(lián)網(wǎng)發(fā)展到繁華的時候:最靠譜的設(shè)備識別方式是解析廣播內(nèi)容,其中有個設(shè)備id,然后拿著這個id,去云端查詢設(shè)備信息,然后配對。但是我目前是靠名字配對的
- GATT 句柄泄漏,不用了就把GATTclose掉,不關(guān)的話,就會重新開GATT,導(dǎo)致系統(tǒng)句柄耗盡,導(dǎo)致沒有辦法創(chuàng)建新的連接
- 重復(fù)連接,打開一個gatt句柄,就會創(chuàng)建一個通道,這個通道是獨立的
- 在高版本的手機上,在發(fā)現(xiàn)新的設(shè)備的過程,還需要運行時的權(quán)限,切記 (Android M 以上)
- gatt的回調(diào)是binder線程池,如何把結(jié)果的數(shù)據(jù)統(tǒng)一的管理,這個問題我想的解決方式動態(tài)代理去放到統(tǒng)一的線程,所以我現(xiàn)在在做這樣的工作
- and so on


