1. 介紹
本篇主要基于 Android 官方的低功耗藍(lán)牙連接服務(wù)。
講解如何通過 UUID 連接藍(lán)牙設(shè)備。如果你針對(duì) GATT 服務(wù)不太了解。那么這篇應(yīng)該能夠稍微幫助到你。
官方文檔地址:https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le?hl=zh_cn#connect
2. 概念
如果是老用戶了,那么就應(yīng)該知道曾經(jīng)藍(lán)牙設(shè)備是一個(gè)高耗電的部件。根本不可能長時(shí)間開啟。而在藍(lán)牙4.0版本之后,藍(lán)牙的通訊,耗電,抗干擾都得到了顯著提升。同時(shí)藍(lán)牙成本也得到了降低。
然后才有了我們現(xiàn)在的各種穿戴設(shè)備例如手環(huán),藍(lán)牙耳機(jī),藍(lán)牙電子秤,藍(lán)牙音箱等等的爆發(fā)。
同時(shí),其他工業(yè)或者外置設(shè)備也都開始大量支持藍(lán)牙通訊。因?yàn)槟芎暮统杀窘档土恕?/p>
針對(duì)低功耗藍(lán)牙通訊,Android 4.3(API 18)開始引入了 BLE 庫。我們可以直接使用 Android SDK 中的藍(lán)牙 BLE 庫,而不用額外導(dǎo)入依賴庫。
以前開發(fā)藍(lán)牙通訊,還需要實(shí)現(xiàn)藍(lán)牙配對(duì)。需要主動(dòng)跳轉(zhuǎn)到手機(jī)設(shè)置界面進(jìn)行PIN碼配對(duì),然后配對(duì)通過之后才能進(jìn)行藍(lán)牙鏈接。
而使用BLE庫,我們可以直接通過藍(lán)牙設(shè)備的UUID進(jìn)行連接(通過GATT服務(wù)),在當(dāng)前應(yīng)用內(nèi)就能直接連接了。而不用通過系統(tǒng)設(shè)置。
市面上的各種手環(huán)的自動(dòng)匹配鏈接,電子秤的自動(dòng)連接等等都是通過GATT進(jìn)行通訊和鏈接的。
2.1 術(shù)語
- GATT:全稱為:Generic Attribute Profile,翻譯為:通用屬性配置文件。GATT 配置文件是一種通用規(guī)范,內(nèi)容針對(duì)在 BLE 鏈路上發(fā)送和接收稱為“屬性ATT”的簡(jiǎn)短數(shù)據(jù)片段。目前所有低功耗應(yīng)用配置文件均以 GATT 為基礎(chǔ)。
- ATT:全稱為:Attribute Protocol,翻譯為:屬性協(xié)議。它是 GATT 的構(gòu)建基礎(chǔ),二者的關(guān)系也被稱為 GATT/ATT。每個(gè)屬性均由通用唯一標(biāo)識(shí)符 (UUID) 進(jìn)行唯一標(biāo)識(shí),后者是用于對(duì)信息進(jìn)行唯一標(biāo)識(shí)的字符串 ID 的 128 位標(biāo)準(zhǔn)化格式。由 ATT 傳輸?shù)膶傩圆捎锰卣骱头?wù)格式。
- 特征 Characteristic: 特征包含一個(gè)值和 0 至多個(gè)描述特征值的描述符。您可將特征理解為類型,后者與類類似。
- 描述符:描述符是描述特征值的已定義屬性。例如,描述符可指定人類可讀的描述、特征值的可接受范圍或特定于特征值的度量單位。
- Service — 服務(wù)是一系列特征。例如,您可能擁有名為“心率監(jiān)測(cè)器”的服務(wù),其中包括“心率測(cè)量”等特征。
以上術(shù)語的介紹來源于Android官網(wǎng)
2.2 通訊過程
假如我們有一個(gè)藍(lán)牙外置設(shè)備(Device),然后有一個(gè)支持藍(lán)牙的移動(dòng)設(shè)備(Phone)。兩者之間的通訊方式步驟是:
- Device 開啟藍(lán)牙。(通常這些設(shè)備都是開機(jī)之后,就默認(rèn)開啟藍(lán)牙了)
- Phone 開啟藍(lán)牙。
- Phone 發(fā)現(xiàn) Device。
- Phone 與 Device 創(chuàng)建藍(lán)牙連接。
- Phone 創(chuàng)建 Gatt 客戶端,與 Device Gatt 服務(wù)端連接。
- Phone 通過 Gatt 服務(wù)功能獲取 Device 中的消息,并發(fā)送消息給 Device 設(shè)備。
整個(gè)過程就是這樣的。下面我也將按照這個(gè)通訊過程進(jìn)行介紹。
3.開發(fā)
基于我的使用情況,從無到有的介紹,完整的藍(lán)牙開發(fā)配置過程。給大家一個(gè)參考
語言主要為 Java
3.1 權(quán)限
要在應(yīng)用中使用藍(lán)牙功能,必須聲明 BLUETOOTH 藍(lán)牙權(quán)限。需要此權(quán)限才能執(zhí)行任何藍(lán)牙通信,例如請(qǐng)求連接、接受連接和傳輸數(shù)據(jù)等。
同時(shí),還需要位置權(quán)限。因?yàn)樗{(lán)牙 LE 信標(biāo)通常與位置相關(guān)聯(lián)。如果不開啟 ACCESS_FINE_LOCATION 權(quán)限。那么我們將會(huì)無法發(fā)現(xiàn)藍(lán)牙設(shè)備。
也就是執(zhí)行藍(lán)牙掃描 API 無法得到任何結(jié)果(PS::Logcat 中的錯(cuò)誤日志會(huì)告訴你,要開啟位置權(quán)限,否則無法掃描發(fā)現(xiàn)藍(lán)牙設(shè)備)。
<!-- 藍(lán)牙搜索配對(duì) -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- 操縱藍(lán)牙的開啟-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 如果應(yīng)用必須安裝在支持藍(lán)牙的設(shè)備上,可以將下面的required的值設(shè)置為true。-->
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />
其中 android.permission.ACCESS_FINE_LOCATION? 是高版本API 28 權(quán)限。如果要支持更低版本,就需要申請(qǐng)<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />。
如果要執(zhí)行藍(lán)牙掃描功能,我們需要申請(qǐng):<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />權(quán)限
如果要執(zhí)行藍(lán)牙鏈接,開關(guān)藍(lán)牙。需要申請(qǐng):<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />權(quán)限
而上面兩個(gè)權(quán)限呢,是在 API 31 上才有效。而低版本就是申請(qǐng):
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />權(quán)限也就夠了。
權(quán)限配置完畢之后,就是代碼開發(fā)了。
不管是高版本,還是低版本。將權(quán)限都申請(qǐng)可以說最穩(wěn)妥了。
3.2 檢測(cè)設(shè)備是否支持藍(lán)牙
通常情況下,手機(jī)是有藍(lán)牙的。而我們?nèi)绻谄渌?Android 系統(tǒng)的設(shè)備中,例如TV,平板,一體機(jī)等等。是否有藍(lán)牙還真不能完整保證。
如果不確定的情況下,那么可以通過以下代碼檢查 BLE 的可用性。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
//不支持藍(lán)牙設(shè)備
finish();
} else {
//支持藍(lán)牙設(shè)備
}
藍(lán)牙是否開啟都不影響檢查結(jié)果。它檢查的是設(shè)備是否有藍(lán)牙功能,而不是藍(lán)牙是否啟動(dòng),下面會(huì)介紹如何判斷藍(lán)牙是否啟動(dòng)
3.3 開啟藍(lán)牙
當(dāng)我們?cè)O(shè)備也支持藍(lán)牙了,權(quán)限也配置了。下一步就是獲取 BluetoothAdapter 對(duì)象了。
final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
我們后續(xù)控制藍(lán)牙的狀態(tài),都是通過該方法實(shí)現(xiàn)的。
首先,檢測(cè)藍(lán)牙是否開啟。可以通過isEnabled()方法進(jìn)行檢測(cè):
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
//開啟設(shè)備的藍(lán)牙鏈接
bluetoothAdapter.enable();//開啟藍(lán)牙
//動(dòng)態(tài)判斷是否擁有位置權(quán)限ACCESS_COARSE_LOCATION 或ACCESS_FINE_LOCATION ,然后再執(zhí)行藍(lán)牙掃描
} else {
//動(dòng)態(tài)判斷是否擁有位置權(quán)限ACCESS_COARSE_LOCATION 或ACCESS_FINE_LOCATION,然后再執(zhí)行藍(lán)牙掃描
}我們其實(shí)可以直接使用bluetoothAdapter.enable()開啟藍(lán)牙。當(dāng)藍(lán)牙沒有開啟時(shí),我們可以直接開啟藍(lán)牙。
這個(gè)方法的結(jié)果,并不是實(shí)時(shí)返回的。我們?nèi)绻浪{(lán)牙是否開啟,需要監(jiān)聽藍(lán)牙狀態(tài)的廣播才行。下面會(huì)介紹廣播監(jiān)聽。
PS:這個(gè)方法需要android.Manifest.permission.BLUETOOTH_CONNECT 權(quán)限才能使用。
官方是建議我們通過Intent讓系統(tǒng)設(shè)置進(jìn)行開啟藍(lán)牙的。
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}但是現(xiàn)在startActivityForResult方法已經(jīng)過時(shí)。我們可以使用Launcher來調(diào)用:
ActivityResultLauncher<Intent> launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
//處理返回結(jié)果
}
});上面的 launcher?需要在Activity? 的 onCreate 方法中初始化。然后在需要進(jìn)行藍(lán)牙設(shè)置界面啟動(dòng)的地方配置:
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); //創(chuàng)建一個(gè)藍(lán)牙啟動(dòng)的意圖
launcher.launch(enableBtIntent);//使用launcer啟動(dòng)這個(gè)意圖就可以了。
我們?nèi)绻褂胋luetoothAdapter.enable();?時(shí)Android Studio出現(xiàn)代碼錯(cuò)誤警告,可以在該代碼使用的方法中添加:@SuppressLint("MissingPermission")注解。
3.4 廣播監(jiān)聽
其實(shí)這個(gè)廣播監(jiān)聽,是否需要。根據(jù)大家實(shí)際情況來定。不一定需要。
首先,創(chuàng)建一個(gè)動(dòng)態(tài)廣播對(duì)象:
public class BluetoothFoundReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//監(jiān)聽藍(lán)牙狀態(tài)之后,發(fā)送消息
try {
if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
//開始掃描
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
//結(jié)束掃描
} else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
//發(fā)現(xiàn)設(shè)備,每掃碼到一個(gè)設(shè)備,都會(huì)觸發(fā)一次
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//我們可以得到藍(lán)牙設(shè)備
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
//藍(lán)牙開關(guān)狀態(tài)
int statue = bluetoothAdapter.getState();
switch (statue) {
case BluetoothAdapter.STATE_OFF:
Log.e(TAG, "藍(lán)牙狀態(tài):,藍(lán)牙關(guān)閉");
break;
case BluetoothAdapter.STATE_ON:
Log.e(TAG, "藍(lán)牙狀態(tài):,藍(lán)牙打開");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Log.e(TAG, "藍(lán)牙狀態(tài):,藍(lán)牙正在關(guān)閉");
break;
case BluetoothAdapter.STATE_TURNING_ON:
Log.e(TAG, "藍(lán)牙狀態(tài):,藍(lán)牙正在打開");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}然后進(jìn)行廣播注冊(cè):
bluetoothFoundReceiver = new BluetoothFoundReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//連接藍(lán)牙,斷開藍(lán)牙
filter.addAction(BluetoothDevice.ACTION_FOUND);//找到設(shè)備的廣播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//搜索完成的廣播
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//狀態(tài)改變 配對(duì)開始時(shí),配對(duì)成功時(shí)
registerReceiver(bluetoothFoundReceiver, filter);
注冊(cè)完畢后,在onDestroy方法中需要注銷注冊(cè):
@Override
protected void onDestroy() {
if (bluetoothFoundReceiver != null)
unregisterReceiver(bluetoothFoundReceiver); //停止監(jiān)聽
super.onDestroy();
}
其實(shí),我們只需要藍(lán)牙狀態(tài)的監(jiān)聽就可以了BluetoothAdapter.ACTION_STATE_CHANGED 其他的設(shè)備查找,配對(duì)。可以不用,因?yàn)橛|發(fā)到廣播的設(shè)備查找效率太低,而且多次重復(fù)查找時(shí),還會(huì)出現(xiàn)耗時(shí)變長。設(shè)備無法查找到的情況。
3.5 藍(lán)牙設(shè)備查找
官方文檔上推薦的查找方式是:
bluetoothAdapter.startLeScan(leScanCallback); //查找
bluetoothAdapter.stopLeScan(leScanCallback); //停止查找
可是現(xiàn)在這個(gè)方法也過時(shí)了。替換方法是:
BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner();
//不進(jìn)行權(quán)限驗(yàn)證
ScanCallback callback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice device = result.getDevice();//得到設(shè)備
// Log.e(TAG, "發(fā)現(xiàn)設(shè)備" + device.getName());
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(TAG, "搜索錯(cuò)誤" + errorCode);
}
};
scanner.startScan(callback);
onScanResult方法是一個(gè)在子線程觸發(fā)的回調(diào),我們不能在該方法中直接操作UI對(duì)象。
其次,掃描到一個(gè)藍(lán)牙設(shè)備就會(huì)觸發(fā)一次消息回調(diào)。我們可以得到一個(gè)BluetoothDevice對(duì)象。 也就是說這個(gè)方法中會(huì)觸發(fā)多次回調(diào),
所以建議,在掃描到我們的藍(lán)牙設(shè)備之后,主動(dòng)調(diào)用scanner.stopScan(callback);停止掃描。
PS:這種查找方式,不會(huì)觸發(fā)藍(lán)牙的遍歷廣播。我們?nèi)绻_啟廣播進(jìn)行監(jiān)聽設(shè)備掃描情況。如果通過startScan方法,廣播中不會(huì)有回調(diào)。
上面是一個(gè)通用搜索模式,我們還可以配置自己的過濾條件。例如:
ScanFilter sn =new ScanFilter.Builder().setDeviceName("藍(lán)牙設(shè)備的名稱").setServiceUuid(ParcelUuid.fromString("我們的設(shè)備的Service UUID")).build();
List<ScanFilter> scanFilters=new ArrayList<>();
scanFilters.add(sn);
scanner.startScan(scanFilters, new ScanSettings.Builder().build(),callback);其中ScanFilter?對(duì)象,我們可以配置我們想查找的藍(lán)牙設(shè)備的信息。可以是setDeviceName,setServiceUuid,setDeviceAddress,setServiceSolicitationUuid等。
ScanSettings對(duì)象是可以定義我們的掃描模式,通過配置該項(xiàng)可以提高掃描效率。
默認(rèn)情況下,執(zhí)行的是:SCAN_MODE_LOW_POWER在低功耗模式下執(zhí)行藍(lán)牙LE掃描。 這是默認(rèn)的掃描模式,因?yàn)樗淖钌俚碾娏俊?/p>
3.5.1 startDiscovery
如果上面的方法還不滿足我們的情況,可以使用:
if (bluetoothAdapter.isDiscovering()) {//是否在掃描
bluetoothAdapter.cancelDiscovery(); //停止掃描
}
//查找藍(lán)牙
bluetoothAdapter.startDiscovery();我們可以直接使用bluetoothAdapter進(jìn)行掃描。這個(gè)方法觸發(fā)之后是由系統(tǒng)進(jìn)行藍(lán)牙掃描。就和我們?cè)谑謾C(jī)的設(shè)置界面中點(diǎn)擊藍(lán)牙掃描一樣。
上面的這個(gè)方法沒有回調(diào),因?yàn)樗械乃{(lán)牙設(shè)備的發(fā)現(xiàn)都將通過廣播事件進(jìn)行傳遞。
需要通過我上面的廣播監(jiān)聽介紹的內(nèi)容。進(jìn)行實(shí)時(shí)獲取到掃描到的設(shè)備。
使用上面的方法有幾個(gè)缺點(diǎn):
1.效率慢,耗時(shí)很長。
2.重復(fù)掃描會(huì)失敗。不能說是失敗了,而是系統(tǒng)會(huì)將重復(fù)掃描的請(qǐng)求進(jìn)行阻止,關(guān)鍵的問題在于這個(gè)阻止操作是手機(jī)廠商定制的。
PS:不管是BluetoothLeScanner? 還是bluetoothAdapter.startDiscovery() 去查找藍(lán)牙設(shè)備。都不建議一直重復(fù)掃描。否則會(huì)出現(xiàn)無法掃描到設(shè)備,沒有任何掃描結(jié)果等等情況。因?yàn)閽呙枋且粋€(gè)耗時(shí)耗電的操作。
3.6 鏈接Gatt
當(dāng)我們掃描到了藍(lán)牙設(shè)備之后,就會(huì)獲取到BluetoothDevice?對(duì)象。然后我們通過BluetoothDevice?對(duì)象創(chuàng)建GATT服務(wù)進(jìn)行后續(xù)的藍(lán)牙通訊。
BluetoothDevice device;// 當(dāng)我們通過掃描得到device對(duì)象之后,進(jìn)行Gatt服務(wù)創(chuàng)建
BluetoothGatt bluetoothGatt = device.connectGatt(this, false, gattCallback);
第一個(gè)傳參context沒有什么可以介紹的。
第二個(gè)傳參autoConnect:是一個(gè)boolean值對(duì)象,false代表直接連接到藍(lán)牙設(shè)備。true代表在藍(lán)牙設(shè)備可用時(shí)自動(dòng)連接。
第三個(gè)參數(shù)BluetoothGattCallback 是Gatt服務(wù)的各種回調(diào)了。
我們通過gattCallback回調(diào)的內(nèi)容,來得到與藍(lán)牙設(shè)備的鏈接狀態(tài),數(shù)據(jù)通信內(nèi)容等。
下面來詳細(xì)介紹下BluetoothGattCallback對(duì)象的幾個(gè)方法。
String SERVICE_UUID="00000-000000-000000-000000";//這個(gè)是我要鏈接的藍(lán)牙設(shè)備的ServiceUUID
BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
//GATT的鏈接狀態(tài)回調(diào)
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
Log.v(TAG, "連接成功");
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.e(TAG, "連接斷開");
} else if (newState == BluetoothProfile.STATE_CONNECTING) {
//TODO 在實(shí)際過程中,該方法并沒有調(diào)用
Log.e(TAG, "連接中....");
}
}
//獲取GATT服務(wù)發(fā)現(xiàn)后的回調(diào)
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "GATT_SUCCESS"); //服務(wù)發(fā)現(xiàn)
for (BluetoothGattService bluetoothGattService : gatt.getServices()) {
Log.e(TAG, "Service_UUID" + bluetoothGattService.getUuid()); // 我們可以遍歷到該藍(lán)牙設(shè)備的全部Service對(duì)象。然后通過比較Service的UUID,我們可以區(qū)分該服務(wù)是屬于什么業(yè)務(wù)的
if (SERVICE_UUID.equals(bluetoothGattService.getUuid().toString())) {
for (BluetoothGattCharacteristic characteristic : bluetoothGattService.getCharacteristics()) {
prepareBroadcastDataNotify(gatt, characteristic); //給滿足條件的屬性配置上消息通知
}
return;//結(jié)束循環(huán)操作
}
}
} else {
Log.e(TAG, "onServicesDiscovered received: " + status);
}
}
//藍(lán)牙設(shè)備發(fā)送消息后的自動(dòng)監(jiān)聽
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
// readUUID 是我要鏈接的藍(lán)牙設(shè)備的消息讀UUID值,跟通知的特性的UUID比較。這樣可以避免其他消息的污染。
if (READ_UUID.equals(characteristic.getUuid().toString())) {
try {
String chara = new String(characteristic.getValue(), "UTF-8");
Log.e(TAG, "消息內(nèi)容:" + chara);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
};
我們可以通過鏈接成功和鏈接斷開。來判斷我們當(dāng)前與藍(lán)牙設(shè)備的通訊狀態(tài)。
當(dāng)我們比對(duì)Service?的UUID成功之后, 我們就可以獲取Service的Characteristic對(duì)象。該對(duì)象也就是特征。通過注冊(cè)特征來實(shí)現(xiàn)消息的監(jiān)聽和發(fā)送業(yè)務(wù)。
3.7 注冊(cè)消息監(jiān)聽-setCharacteristicNotification
@SuppressLint("MissingPermission")
private void prepareBroadcastDataNotify(BluetoothGatt mBluetoothGatt, BluetoothGattCharacteristic characteristic) {
Log.e(TAG, "CharacteristicUUID:" + characteristic.getUuid().toString());
int charaProp = characteristic.getProperties();
//判斷屬性是否支持消息通知
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
BluetoothGattDescriptor descriptor =
characteristic.getDescriptor(UUID.fromString(UUIDManager.READ_DEDSCRIPTION_UUID));
if (descriptor != null) {
//注冊(cè)消息通知
mBluetoothGatt.setCharacteristicNotification(characteristic, true);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
}在上面的示例中:READ_DEDSCRIPTION_UUID = "00002902-0000-1000-8000-00805f9b34fb" 是固定的,不管你鏈接什么樣的藍(lán)牙設(shè)備。
在注冊(cè)消息監(jiān)聽,都是使用UUID值是00002902-0000-1000-8000-00805f9b34fb進(jìn)行的。這個(gè)是Android系統(tǒng)保留的。用于動(dòng)態(tài)監(jiān)聽的。
你如果不想使用這個(gè)動(dòng)態(tài)監(jiān)聽。就需要自己寫線程主動(dòng)去輪詢獲取到藍(lán)牙設(shè)備發(fā)送過來的消息了。
到這里,我們其實(shí)就能夠?qū)崿F(xiàn)藍(lán)牙設(shè)備的實(shí)時(shí)監(jiān)聽,并得到消息內(nèi)容了。
3.8 寫數(shù)據(jù)到藍(lán)牙設(shè)備中
我們?nèi)绻雽?nèi)容推送到藍(lán)牙設(shè)備中,在發(fā)現(xiàn)服務(wù)的時(shí)候onServicesDiscovered 遍歷特性中,確保是用于寫消息的特性對(duì)象后。選擇持有該特性,然后通過:
String data ="0x12";
BluetoothGattCharacteristic writeCharact = bluetoothGattService.
getCharacteristic(UUID.fromString(WRITE_UUID));
//查找UUID是寫的特性,并檢測(cè)是否擁有寫權(quán)限
if (writeCharact == null || writeCharact.getProperties() != BluetoothGattCharacteristic.PROPERTY_WRITE) {
return ;//該特性沒有寫的權(quán)限。所以無法傳入
}
// 當(dāng)數(shù)據(jù)傳遞到藍(lán)牙之后
// 會(huì)回調(diào)BluetoothGattCallback里面的write方法
writeCharact.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
// 將需要傳遞的數(shù)據(jù)轉(zhuǎn)為16進(jìn)制數(shù)
writeCharact.setValue(data);
bluetoothGatt.writeCharacteristic(writeCharact);
3.9 關(guān)閉連接
當(dāng)藍(lán)牙通訊結(jié)束,或者界面關(guān)閉時(shí)。我們需要關(guān)閉GATT服務(wù),減少資源占用。
if (bluetoothGatt != null) {
bluetoothGatt.close();
bluetoothGatt.disconnect();
bluetoothGatt = null;
}也可以關(guān)閉BluetoothGattCallback 的回調(diào)監(jiān)聽:
gattCallback.disConnectBlue();//關(guān)閉GATT服務(wù)回調(diào)監(jiān)聽
4. 小結(jié)
到這里藍(lán)牙的鏈接和讀取就結(jié)束了。
我們通過bluetoothAdapter 查找到藍(lán)牙設(shè)備之后,再通過GATT服務(wù)進(jìn)行藍(lán)牙設(shè)備與手機(jī)之間的配對(duì)。直接比對(duì)UUID,而不再需要PIN碼進(jìn)行配對(duì)了。
(PS:有些安全性要求比較高的設(shè)備,還是會(huì)需要主動(dòng)進(jìn)行PIN碼配對(duì)。PIN配隊(duì)就只能通過系統(tǒng)設(shè)備界面中的藍(lán)牙功能項(xiàng)進(jìn)行操作了。)
通過GATT服務(wù)連接成功后。就可以查詢?cè)揝erver下的各種特性了,不同的特性對(duì)應(yīng)了一個(gè)功能。有發(fā)消息的特性,也有用于收消息的特性。
同時(shí)一個(gè)藍(lán)牙設(shè)備對(duì)象,可能有多種服務(wù)功能。
如果不想自己寫線程變量輪詢?cè)O(shè)備發(fā)送過來的消息,就通過注冊(cè)消息監(jiān)聽。讓BLE框架幫我們進(jìn)行輪詢之后,再通知到我們。