android藍牙BLE(一) —— 掃描android
想了解藍牙通訊以前,須要先了解藍牙兩個最基本的協議:GAP 和 GATT。ide
GAP是通用訪問配置文件的首字母縮寫,主要控制藍牙鏈接和廣播。GAP使藍牙設備對外界可見,並決定設備是否能夠或者怎樣與其餘設備進行交互。
oop
GAP定義了多種角色,但主要的兩個是:中心設備 和 外圍設備。
post
中心設備:能夠掃描並鏈接多個外圍設備,從外設中獲取信息。
ui
外圍設備:小型,低功耗,資源有限的設備。能夠鏈接到功能更強大的中心設備,併爲其提供數據。spa
GAP 中外圍設備經過兩種方式向外廣播數據:廣播數據 和 掃描回覆。 每種數據最長能夠包含 31 byte。
線程
廣播數據是必需的,由於外設必需不停的向外廣播,讓中心設備知道它的存在。
掃描回覆是可選的,中心設備能夠向外設請求掃描回覆,這裏包含一些設備額外的信息。
外設經過廣播本身讓中心設備發現本身,並創建 GATT 鏈接,從而進行更多的數據交換。但有些狀況是不須要鏈接的,只要外設廣播本身的數據便可。目的是讓外圍設備,把本身的信息發送給多箇中心設備。由於基於 GATT 鏈接的方式的,只能是一個外設鏈接一箇中心設備。
GATT配置文件是一個通用規範,用於在BLE鏈路上發送和接收被稱爲「屬性」的數據塊。目前全部的BLE應用都基於GATT。
BLE設備經過叫作 Service 和 Characteristic 的東西進行通訊
GATT使用了 ATT(Attribute Protocol)協議,ATT 協議把 Service, Characteristic對應的數據保存在一個查詢表中,次查找表使用 16 bit ID 做爲每一項的索引。
GATT 鏈接是獨佔的。也就是一個 BLE 外設同時只能被一箇中心設備鏈接。一旦外設被鏈接,它就會立刻中止廣播,這樣它就對其餘設備不可見了。當外設與中心設備斷開,外設又開始廣播,讓其餘中心設備感知該外設的存在。而中心設備可同時與多個外設進行鏈接。
中心設備和外設須要雙向通訊的話,惟一的方式就是創建 GATT 鏈接。
GATT 通訊的雙方是 C/S 關係。外設做爲 GATT 服務端(Server),它維持了 ATT 的查找表以及 service 和 characteristic 的定義。中心設備是 GATT 客戶端(Client),它向 外設(Server) 發起請求來獲取數據。
Profile:並非實際存在於 BLE 外設上的,它只是一個被 Bluetooth SIG 或者外設設計者預先定義的 Service 的集合。例如心率Profile(Heart Rate Profile)就是結合了 Heart Rate Service 和 Device Information Service。
Service:包含一個或者多個 Characteristic。每一個 Service 有一個 UUID 惟一標識。
Characteristic: 是最小的邏輯數據單元。一個Characteristic包括一個單一value變量和0-n個用來描述characteristic變量的Descriptor。與 Service 相似,每一個 Characteristic 用 16 bit 或者 128 bit 的 UUID 惟一標識。
實際開發中,和 BLE 外設打交道,主要是經過 Characteristic。能夠從 Characteristic 讀取數據,也能夠往 Characteristic 寫數據,從而實現雙向的通訊。
UUID 有 16 bit 、32bit 和 128 bit 的。16 bit 的 UUID 是官方經過認證的,須要花錢購買。 Bluetooth_Base_UUID定義爲 00000000-0000-1000-8000-00805F9B34FB
若16 bit UUID爲xxxx,轉換爲128 bit UUID爲0000xxxx-0000-1000-8000-00805F9B34FB
若32 bit UUID爲xxxxxxxx,轉換爲128 bit UUID爲xxxxxxxx-0000-1000-8000-00805F9B34FB
簡單介紹BLE開發當中各類主要類和其做用:
BluetoothDeivce:藍牙設備,表明一個具體的藍牙外設。
BluetoothGatt:通用屬性協議,定義了BLE通信的基本規則和操做
BluetoothGattCallback:GATT通訊回調類,用於回調的各類狀態和結果。
BluetoothGattService:服務,由零或多個特徵組構成。
BluetoothGattCharacteristic:特徵,裏面包含了一組或多組數據,是GATT通訊中的最小數據單元。
BluetoothGattDescriptor:特徵描述符,對特徵的額外描述,包括但不只限於特徵的單位,屬性等。
對掃描到的藍牙能夠用集合形式進行緩存,也可只保存其mac地址,存儲到字符集合中,用於後續的鏈接。
1.一、根據mac地址獲取到BluetoothDeivce用於鏈接
BluetoothManager bluetoothmanager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothmanager.getAdapter();
//獲取藍牙設備對象進行鏈接
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(macAddressStr)
複製代碼
1.二、藍牙gatt回調:
實現BluetoothGattCallBack類,監聽藍牙鏈接過程當中各類回調的監聽。
藍牙Gatt回調方法中都不能夠進行耗時操做,須要將其方法內進行的操做丟進另外一個線程,儘快返回。
//定義子線程handle,用於在BluetoothGattCallback中回調方法中的操做拋到該線程工做。
private Handler mHandler;
//定義handler工做的子線程
private HandlerThread mHandlerThread;
初始化handler
mHandlerThread = new HandlerThread("daqi");
mHandlerThread.start();
//將handler綁定到子線程中
mHandler = new Handler(mHandlerThread.getLooper());
//定義藍牙Gatt回調類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//鏈接狀態回調
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
// status 用於返回操做是否成功,會返回異常碼。
// newState 返回鏈接狀態,如BluetoothProfile#STATE_DISCONNECTED、BluetoothProfile#STATE_CONNECTED
//操做成功的狀況下
if (status == BluetoothGatt.GATT_SUCCESS){
//判斷是否鏈接碼
if (newState == BluetoothProfile.STATE_CONNECTED) {
}else if(newState == BluetoothProfile.STATE_DISCONNECTED){
//判斷是否斷開鏈接碼
}
}else{
//異常碼
}
}
//服務發現回調
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
}
//特徵寫入回調
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
//外設特徵值改變回調
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
//描述寫入回調
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
}
複製代碼
1.三、鏈接設備:
調用BluetoothDevice#connectGatt()進行ble鏈接,第二個參數默認選擇false,不自動鏈接。並定義BluetoothGatt變量,存儲BluetoothDevice#connectGatt()返回的對象。
//定義Gatt實現類
private BluetoothGatt mBluetoothGatt;
//建立Gatt回調
private BluetoothGattCallback mGattCallback = new daqiBluetoothGattCallback();
//鏈接設備
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mBluetoothGatt = mBluetoothDevice.connectGatt(mContext,
false, mGattCallback, BluetoothDevice.TRANSPORT_LE);
} else {
mBluetoothGatt = mBluetoothDevice.connectGatt(mContext, false, mGattCallback);
}
複製代碼
1.四、鏈接異常處理:
藍牙鏈接時,不必定百分百鏈接成功。鏈接出錯時,會返回異常碼進行錯誤描述。
對於大多數異常碼,能夠經過重連來達到鏈接成功的目的。
錯誤代碼:
133 :鏈接超時或未找到設備。
8 : 設備超出範圍
22 :表示本地設備終止了鏈接
//定義重連次數
private int reConnectionNum = 0;
//最多重連次數
private int maxConnectionNum = 3;
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//鏈接狀態回調
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
// status 用於返回操做是否成功,會返回異常碼。
//操做成功的狀況下
if (status == BluetoothGatt.GATT_SUCCESS){
}else{
//重連次數不大於最大重連次數
if(reConnectionNum < maxConnectionNum){
//重連次數自增
reConnectionNum++
//鏈接設備
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mBluetoothGatt = mBluetoothDevice.connectGatt(mContext,
false, mGattCallback, BluetoothDevice.TRANSPORT_LE);
} else {
mBluetoothGatt = mBluetoothDevice.connectGatt(mContext, false, mGattCallback);
}
}else{
//斷開鏈接,返回鏈接失敗回調
}
}
}
//其餘回調方法
}
複製代碼
鏈接成功後,觸發BluetoothGattCallback#onConnectionStateChange()方法。
2.一、發現服務:
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//鏈接狀態回調
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
// status 用於返回操做是否成功,會返回異常碼。
//操做成功的狀況下
if (status == BluetoothGatt.GATT_SUCCESS){
//判斷是否鏈接碼
if (newState == BluetoothProfile.STATE_CONNECTED) {
//可延遲發現服務,也可不延遲
mHandler.post(() ->
//發現服務
mBluetoothGatt.discoverServices();
);
}else if(newState == BluetoothProfile.STATE_DISCONNECTED){
//判斷是否斷開鏈接碼
}
}
}
//其餘回調方法
}
複製代碼
當發現服務成功後,會觸發BluetoothGattCallback#onServicesDiscovered()回調:
//定義須要進行通訊的ServiceUUID
private UUID mServiceUUID = UUID.fromString("0000xxxx-0000-1000-8000-00805f9b34fb");
//定義藍牙Gatt回調類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//服務發現回調
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
mHandler.post(() ->
//獲取指定uuid的service
BluetoothGattService gattService = mBluetoothGatt.getService(mServiceUUID);
//獲取到特定的服務不爲空
if(gattService != null){
}else{
//獲取特定服務失敗
}
);
}
}
}
複製代碼
2.二、發現服務失敗:
發現服務時,會存在發現不了特定服務的狀況。或者說,整個BluetoothGatt對象中的服務列表爲空。
BluetoothGatt類中存在一個隱藏的方法refresh(),用於刷新Gatt的服務列表。當發現不了服務時,能夠經過反射去調用該方法。
3.一、讀寫特定特徵:
//定義須要進行通訊的ServiceUUID
private UUID mServiceUUID = UUID.fromString("0000xxxx-0000-1000-8000-00805f9b34fb");
//定義須要進行通訊的CharacteristicUUID
private UUID mCharacteristicUUID = UUID.fromString("0000yyyy-0000-1000-8000-00805f9b34fb");
//定義藍牙Gatt回調類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//服務發現回調
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
mHandler.post(() ->
//獲取指定uuid的service
BluetoothGattService gattService = mBluetoothGatt.getService(mServiceUUID);
//獲取到特定的服務不爲空
if(gattService != null){
//獲取指定uuid的Characteristic
BluetoothGattCharacteristic gattCharacteristic = gattService.getCharacteristic(mCharacteristicUUID);
//獲取特定特徵成功
if(gattCharacteristic != null){
//寫入你須要傳遞給外設的特徵值(即傳遞給外設的信息)
gattCharacteristic.setValue(bytes);
//經過GATt實體類將,特徵值寫入到外設中。
mBluetoothGatt.writeCharacteristic(gattCharacteristic);
//若是隻是須要讀取外設的特徵值:
//經過Gatt對象讀取特定特徵(Characteristic)的特徵值
String readValue = mBluetoothGatt.readCharacteristic(gattCharacteristic);
}
}else{
//獲取特定服務失敗
}
);
}
}
}
複製代碼
當成功讀取特徵值時,會觸發BluetoothGattCallback#onCharacteristicRead()回調。
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取讀取到的特徵值
characteristic.getValue()
}
}
複製代碼
當成功寫入特徵值到外設時,會觸發BluetoothGattCallback#onCharacteristicWrite()回調。
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取寫入到外設的特徵值
characteristic.getValue()
}
}
複製代碼
當寫入完特徵值後,外設會修改本身的特徵值,手機會觸發BluetoothGattCallback#onCharacteristicChanged()方法,進行雙向通訊。
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取外設修改的特徵值
String value = characteristic.getValue()
//對特徵值進行解析
}
}
複製代碼
斷開鏈接的操做分爲兩步:
一、mBluetoothGatt.disconnect();
二、mBluetoothGatt.close();
調用disconnect()後,會觸發手機會觸發BluetoothGattCallback#onConnectionStateChange()的回調,回調斷開鏈接信息,newState = BluetoothProfile.STATE_DISCONNECTED。但接着立刻調用close(),會終止BluetoothGattCallback#onConnectionStateChange()的回調,能夠看狀況將兩個進行拆分調用,但通常都是成對出現。
例如:
須要在外設修改特徵值觸發BluetoothGattCallback#onCharacteristicChanged()時,斷開鏈接。能夠先在BluetoothGattCallback#onCharacteristicChanged()中調用disconnect(),並等調用BluetoothGattCallback#onConnectionStateChange()回調,返回斷開鏈接信息後,再調用close()對Gatt資源進行關閉。
當和外設進行ble通訊時,如出現任何意外狀況,立刻調用斷開鏈接操做。