一篇了解 BLE 藍(lán)牙開發(fā)詳解
前言
有老鐵們私信,要講解下藍(lán)牙開發(fā),那么今天來了;
Android 4.3(API Level 18)開始引入Bluetooth Low Energy(BLE,低功耗藍(lán)牙)的核心功能并提供了相應(yīng)的 API, 應(yīng)用程序通過這些 API 掃描藍(lán)牙設(shè)備、查詢 services、讀寫設(shè)備的 characteristics(屬性特征)等操作。
BLE低功耗藍(lán)牙,主要特點(diǎn)是快速搜索,快速連接,超低功耗保持連接和數(shù)據(jù)傳輸;
一、BLE開發(fā)流程
1、申請權(quán)限
安卓手機(jī)涉及藍(lán)牙權(quán)限問題,藍(lán)牙開發(fā)需要在AndroidManifest.xml文件中添加權(quán)限聲明:
- <!-- 藍(lán)牙權(quán)限 -->
 - <uses-permission android:name="android.permission.BLUETOOTH" />
 - <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
 - 為適配安卓6.0以及以上版本需要添加一個模糊定位的權(quán)限
 - <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 - 手機(jī)權(quán)限管理中允許此權(quán)限,否則會出現(xiàn)無法搜索到設(shè)備的情況;
 
為適配安卓6.0以及以上版本需要添加一個模糊定位的權(quán)限
手機(jī)權(quán)限管理中允許此權(quán)限,否則會出現(xiàn)無法搜索到設(shè)備的情況;
2、打開藍(lán)牙
在搜索設(shè)備之前需要詢問打開手機(jī)藍(lán)牙:
- //獲取系統(tǒng)藍(lán)牙適配器管理類
 - private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter
 - .getDefaultAdapter();
 - // 詢問打開藍(lán)牙
 - if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
 - Intent enableBtIntent = new Intent(
 - BluetoothAdapter.ACTION_REQUEST_ENABLE);
 - startActivityForResult(enableBtIntent, 1);
 - }
 - // 申請打開藍(lán)牙請求的回調(diào)
 - @Override
 - protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 - // TODO Auto-generated method stub
 - super.onActivityResult(requestCode, resultCode, data);
 - if (requestCode == 1) {
 - if (resultCode == RESULT_OK) {
 - Toast.makeText(this, "藍(lán)牙已經(jīng)開啟", Toast.LENGTH_SHORT).show();
 - } else if (resultCode == RESULT_CANCELED) {
 - Toast.makeText(this, "沒有藍(lán)牙權(quán)限", Toast.LENGTH_SHORT).show();
 - finish();
 - }
 - }
 - }
 
3、搜索設(shè)備
- mBluetoothAdapter.startLeScan(callback);
 - private LeScanCallback callback = new LeScanCallback() {
 - @Override
 - public void onLeScan(BluetoothDevice device, int arg1, byte[] arg2) {
 - //device為掃描到的BLE設(shè)備
 - if(device.getName() == "目標(biāo)設(shè)備名稱"){
 - //獲取目標(biāo)設(shè)備
 - targetDevice = device;
 - }
 - }
 - };
 
4、連接設(shè)備
通過掃描BLE設(shè)備,根據(jù)設(shè)備名稱區(qū)分出目標(biāo)設(shè)備targetDevice,下一步實(shí)現(xiàn)與目標(biāo)設(shè)備的連接,在連接設(shè)備之前要停止搜索藍(lán)牙;
mBluetoothAdapter.stopLeScan(callback);
停止搜索一般需要一定的時間來完成,最好調(diào)用停止搜索函數(shù)之后加以100ms的延時,保證系統(tǒng)能夠完全停止搜索藍(lán)牙設(shè)備。停止搜索之后啟動連接過程;
BLE藍(lán)牙的連接方法相對簡單只需調(diào)用connectGatt方法;
- mBluetoothAdapter.startLeScan(callback);
 - private LeScanCallback callback = new LeScanCallback() {
 - @Override
 - public void onLeScan(BluetoothDevice device, int arg1, byte[] arg2) {
 - //device為掃描到的BLE設(shè)備
 - if(device.getName() == "目標(biāo)設(shè)備名稱"){
 - //獲取目標(biāo)設(shè)備
 - targetDevice = device;
 - }
 - }
 - };
 
參數(shù)說明
返回值 BluetoothGatt: BLE藍(lán)牙連接管理類,主要負(fù)責(zé)與設(shè)備進(jìn)行通信;
boolean autoConnect:建議置為false,能夠提升連接速度;
BluetoothGattCallback callback 連接回調(diào),重要參數(shù),BLE通信的核心部分;
5、設(shè)備通信
與設(shè)備建立連接之后與設(shè)備通信,整個通信過程都是在BluetoothGattCallback的異步回調(diào)函數(shù)中完成;
BluetoothGattCallback中主要回調(diào)函數(shù)如下:
- private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
 - @Override
 - public void onConnectionStateChange(BluetoothGatt gatt, int status,
 - int newState) {
 - }
 - @Override
 - public void onCharacteristicWrite(BluetoothGatt gatt,
 - BluetoothGattCharacteristic characteristic, int status) {
 - super.onCharacteristicWrite(gatt, characteristic, status);
 - }
 - @Override
 - public void onDescriptorWrite(BluetoothGatt gatt,
 - BluetoothGattDescriptor descriptor, int status) {
 - };
 - @Override
 - public void onServicesDiscovered(BluetoothGatt gatt, int status) {
 - }
 - @Override
 - public void onCharacteristicChanged(BluetoothGatt gatt,
 - BluetoothGattCharacteristic characteristic) {
 - }
 - };
 
上述幾個回調(diào)函數(shù)是BLE開發(fā)中不可缺少的;
6、等待設(shè)備連接成功
當(dāng)調(diào)用targetdDevice.connectGatt(context, false, gattCallback)后系統(tǒng)會主動發(fā)起與BLE藍(lán)牙設(shè)備的連接,若成功連接到設(shè)備將回調(diào)onConnectionStateChange方法,其處理過程如下:
- @Override
 - public void onConnectionStateChange(BluetoothGatt gatt, int status,
 - int newState) {
 - if (newState == BluetoothGatt.STATE_CONNECTED) {
 - Log.e(TAG, "設(shè)備連接上 開始掃描服務(wù)");
 - // 開始掃描服務(wù),安卓藍(lán)牙開發(fā)重要步驟之一
 - mBluetoothGatt.discoverServices();
 - }
 - if (newState == BluetoothGatt.STATE_DISCONNECTED) {
 - // 連接斷開
 - /*連接斷開后的相應(yīng)處理*/
 - }
 - };
 
判斷newState == BluetoothGatt.STATE_CONNECTED表明此時已經(jīng)成功連接到設(shè)備;
7、開啟掃描服務(wù)
mBluetoothGatt.discoverServices();
掃描BLE設(shè)備服務(wù)是安卓系統(tǒng)中關(guān)于BLE藍(lán)牙開發(fā)的重要一步,一般在設(shè)備連接成功后調(diào)用,掃描到設(shè)備服務(wù)后回調(diào)onServicesDiscovered()函數(shù),函數(shù)原型如下:
- @Override
 - public void onServicesDiscovered(BluetoothGatt gatt, int status) {
 - private List<BluetoothGattService> servicesList;
 - //獲取服務(wù)列表
 - servicesList = mBluetoothGatt.getServices();
 - }
 
- BLE藍(lán)牙協(xié)議下數(shù)據(jù)的通信方式采用BluetoothGattService、BluetoothGattCharacteristic和BluetoothGattDescriptor三個主要的類實(shí)現(xiàn)通信;
 - BluetoothGattService 簡稱服務(wù),是構(gòu)成BLE設(shè)備協(xié)議棧的組成單位,一個藍(lán)牙設(shè)備協(xié)議棧一般由一個或者多個BluetoothGattService組成;
 - BluetoothGattCharacteristic 簡稱特征,一個服務(wù)包含一個或者多個特征,特征作為數(shù)據(jù)的基本單元;
 - 一個BluetoothGattCharacteristic特征包含一個數(shù)據(jù)值和附加的關(guān)于特征的描述
 - BluetoothGattDescriptor:用于描述特征的類,其同樣包含一個value值;
 
8、獲取負(fù)責(zé)通信的BluetoothGattCharacteristic
BLE藍(lán)牙開發(fā)主要有負(fù)責(zé)通信的BluetoothGattService完成的。當(dāng)且稱為通信服務(wù)。通信服務(wù)通過硬件工程師提供的UUID獲取。獲取方式如下:
- BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("藍(lán)牙模塊提供的負(fù)責(zé)通信UUID字符串"));
 - 通信服務(wù)中包含負(fù)責(zé)讀寫的BluetoothGattCharacteristic,且分別稱為notifyCharacteristic和writeCharacteristic。其中notifyCharacteristic負(fù)責(zé)開啟監(jiān)聽,也就是啟動收數(shù)據(jù)的通道,writeCharacteristic負(fù)責(zé)寫入數(shù)據(jù);
 
具體操作方式如下:
- BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("藍(lán)牙模塊提供的負(fù)責(zé)通信服務(wù)UUID字符串"));
 - // 例如形式如:49535343-fe7d-4ae5-8fa9-9fafd205e455
 - notifyCharacteristic = service.getCharacteristic(UUID.fromString("notify uuid"));
 - writeCharacteristic = service.getCharacteristic(UUID.fromString("write uuid"));
 
9、開啟監(jiān)聽
開啟監(jiān)聽,即建立與設(shè)備的通信的首發(fā)數(shù)據(jù)通道,BLE開發(fā)中只有當(dāng)上位機(jī)成功開啟監(jiān)聽后才能與下位機(jī)收發(fā)數(shù)據(jù)。開啟監(jiān)聽的方式如下:
- mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true)
 - BluetoothGattDescriptor descriptor = characteristic
 - .getDescriptor(UUID
 - .fromString
 - ("00002902-0000-1000-8000-00805f9b34fb"));
 - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
 
若開啟監(jiān)聽成功則會回調(diào)BluetoothGattCallback中的onDescriptorWrite()方法,處理方式如下:
- @Override
 - public void onDescriptorWrite(BluetoothGatt gatt,
 - BluetoothGattDescriptor descriptor, int status) {
 - if (status == BluetoothGatt.GATT_SUCCESS) {
 - //開啟監(jiān)聽成功,可以像設(shè)備寫入命令了
 - Log.e(TAG, "開啟監(jiān)聽成功");
 - }
 - };
 
10、寫入數(shù)據(jù)
監(jiān)聽成功后通過向 writeCharacteristic寫入數(shù)據(jù)實(shí)現(xiàn)與下位機(jī)的通信。寫入方式如下:
- //value為上位機(jī)向下位機(jī)發(fā)送的指令
 - writeCharacteristic.setValue(value);
 - mBluetoothGatt.writeCharacteristic(writeCharacteristic)
 
其中:value一般為Hex格式指令,其內(nèi)容由設(shè)備通信的藍(lán)牙通信協(xié)議規(guī)定;
11、接收數(shù)據(jù)
若寫入指令成功則回調(diào)BluetoothGattCallback中的onCharacteristicWrite()方法,說明將數(shù)據(jù)已經(jīng)發(fā)送給下位機(jī);
- @Override
 - public void onCharacteristicWrite(BluetoothGatt gatt,
 - BluetoothGattCharacteristic characteristic, int status) {
 - if (status == BluetoothGatt.GATT_SUCCESS) {
 - Log.e(TAG, "發(fā)送成功");
 - }
 - super.onCharacteristicWrite(gatt, characteristic, status);
 - }
 
若發(fā)送的數(shù)據(jù)符合通信協(xié)議,則下位機(jī)會向上位機(jī)回復(fù)相應(yīng)的數(shù)據(jù)。發(fā)送的數(shù)據(jù)通過回調(diào)onCharacteristicChanged()方法獲取,其處理方式如下:
- @Override
 - public void onCharacteristicChanged(BluetoothGatt gatt,
 - BluetoothGattCharacteristic characteristic) {
 - // value為設(shè)備發(fā)送的數(shù)據(jù),根據(jù)數(shù)據(jù)協(xié)議進(jìn)行解析
 - byte[] value = characteristic.getValue();
 - }
 
通過向下位機(jī)發(fā)送指令獲取下位機(jī)的回復(fù)數(shù)據(jù),即可完成與設(shè)備的通信過程;
12、斷開連接
當(dāng)與設(shè)備完成通信之后之后一定要斷開與設(shè)備的連接。調(diào)用以下方法斷開與設(shè)備的連接:
- mBluetoothGatt.disconnect();
 - mBluetoothGatt.close();
 
二、藍(lán)牙操作的注意事項(xiàng)
- 藍(lán)牙的寫入操作, 讀取操作必須序列化進(jìn)行. 寫入數(shù)據(jù)和讀取數(shù)據(jù)是不能同時進(jìn)行的, 如果調(diào)用了寫入數(shù)據(jù)的方法, 馬上調(diào)用又調(diào)用寫入數(shù)據(jù)或者讀取數(shù)據(jù)的方法,第二次調(diào)用的方法會立即返回 false, 代表當(dāng)前無法進(jìn)行操作;
 - Android 連接外圍設(shè)備的數(shù)量有限,當(dāng)不需要連接藍(lán)牙設(shè)備的時候,必須調(diào)用 BluetoothGatt#close 方法釋放資源;
 - 藍(lán)牙 API 連接藍(lán)牙設(shè)備的超時時間大概在 20s 左右,具體時間看系統(tǒng)實(shí)現(xiàn)。有時候某些設(shè)備進(jìn)行藍(lán)牙連接的時間會很長,大概十多秒。如果自己手動設(shè)置了連接超時時間在某些設(shè)備上可能會導(dǎo)致接下來幾次的連接嘗試都會在 BluetoothGattCallback#onConnectionStateChange 返回 state == 133;
 - 所有的藍(lán)牙操作使用 Handler 固定在一條線程操作,這樣能省去很多因?yàn)榫€程不同步導(dǎo)致的麻煩;
 
總結(jié)
藍(lán)牙開發(fā)中有很多問題,要靜下心分析問題,肯定可以解決的,一起加油;
















 
 
 








 
 
 
 