有不少帖子都在說Android藍牙開發的方法,可是對於其中的概念以及做用時間一直沒有不是很清楚,下邊整理一下相關概念性的東西,記錄一下。android
藍牙鏈接傳輸數據的過程當中,會用到如下幾個概念:服務,特性,描述。一個藍牙設備會有多個服務,每個服務都是一類操做;在這類操做下會存在幾個不一樣的值須要讀寫或者通知,每個值對應惟一一個標記,該標記便是特徵值(特性characteristic),個人理解是鍵值(key);每個特徵值又有多個不一樣的屬性c#
描述(descriptor): 描述是某個特徵值的一個屬性。每一個特徵值有其指定的屬性,例如長度(size);權限(permission):屬性訪問權限,通常有Read、Write、Notifications、Indications;值(value):屬性的值,通常是咱們向設備寫入的數據或者設備通知出來的數據;描述(descriptor);數組
特性(characteristic): 開發過程當中定義的一種參數,能夠對其進行讀、寫、通知操做,對應的就是咱們須要的數據;bash
服務(service): 在存在多個特性值的狀況下,通常會對其進行分類,每個分類便是一個服務(service)app
以上三個概念每個都使用惟一的UUID指定。異步
屬性定義以下這般: ide
Handler:是屬性表的一個句柄(索引),在程序裏只是一個數組,這個值咱們不須要專門去處理;Type:屬性的類型,即UUID,藍牙標準組織對UUID進行了分類,以下:ui
0x1800 - 0x26FF 用做服務類通用惟一識別碼
0x2700 - 0x27FF 用於標識計量單位
0x2800 - 0x28FF 用於區分屬性類型
0x2900 - 0x29FF 用做特性描述
0x2A00 - 0x7FFF 用於區分特性類型
複製代碼
作藍牙BLE開發過程當中有如下類須要使用spa
BluetoothGatt: 通用屬性協議。定義BLE通信的基本規則,對通信過程最上層的封裝,例如從新鏈接藍牙設備,發現藍牙設備的Service等;線程
BluetoothGattService: 服務。經過BluetoothGatt實例調用getService(UUID)獲取,即前面說的對特性分組的服務;用於獲取和管理其包含的特性;
BluetoothGattCharacteristic: 特性。經過BluetoothGattService實例調用getCharacteristic(UUID)獲取,是GATT通信中的最小數據單元;咱們想藍牙設備發送數據、接收藍牙設備的通知都須要用到,是進行通信的最小的操做對象;
BluetoothGattDescriptor: 特性描述符。對特性的額外描述,包括但不只限於特徵的單位、屬性等。
BluetoothDevice: 表明一個遠程藍牙設備。這個類可使咱們鏈接其表明的藍牙設備或者獲取一些有關它的信息,例如它的名字、地址和綁定狀態等;
BluetoothAdapter: Android中的藍牙適配器。用於操做藍牙硬件,例如開啓藍牙掃描,根據已知MAC地址實例化一個BluetoothDevice用於鏈接藍牙設備的操做等。
以上就是在藍牙BLE通訊過程當中須要用到的一些類和概念。
具體看JBD的Android BLE 藍牙開發入門
<uses-permission android:name="android.permission.BLUETOOTH"/> 使用藍牙所須要的權限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> 使用掃描和設置藍牙的權限(申明這一個權限必須申明上面一個權限)
複製代碼
以上BLUETOOTH和BLUETOOTH_ADMIN兩個權限是藍牙開發必須的,同時不一樣的SDK版本還會要求其餘的權限。在Android 5.0 以後,須要在manifest中聲明GPS硬件模塊功能的使用。
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
複製代碼
在Android 6.0及以上,還須要打開位置權限。若是應用沒有位置權限,藍牙掃描功能不能使用(其它藍牙操做例如鏈接藍牙設備和寫入數據不受影響)。同時位置權限是敏感權限,須要進行權限動態申請及判斷。
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
複製代碼
外圍設備開啓藍牙後,會廣播出許多的關於該設備的數據信息,例如 mac 地址,uuid 等等。經過這些數據咱們能夠篩選出須要的設備。使用BluetoothAdapter實例掃描藍牙設備,低版本SDK中有兩個方法能夠調用
//掃描含有特定UUID Service的藍牙設備
boolean startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)
//掃描所有藍牙設備
boolean startLeScan(BluetoothAdapter.LeScanCallback callback)
複製代碼
在SDK>=21時這兩個方法已經被廢棄,官方建議使用BluetoothLeScanner中的如下方法。
//該方法是在API版本26開始添加的,能夠在主程序再也不運行的時候再後臺進行掃描操做,當發現符合過濾條件的設備時會向PendingIntent指定的操做發送通知,進行後續操做。
int startScan (List<ScanFilter> filters,ScanSettings settings,PendingIntent callbackIntent)
//從API版本21開始添加
//搜索所有藍牙設備,使用默認掃描設置,而且沒有掃描過濾器
void startScan (ScanCallback callback)
//從API版本21開始添加
//搜索符合過濾條件的設備
void startScan (List<ScanFilter> filters,ScanSettings settings,ScanCallback callback)
複製代碼
ScanFilter: 是一個藍牙掃描過濾器,能夠指定掃描條件,該參數能夠爲空,具體支持以下,後邊兩個應該不常使用,翻譯可能不太準確:
ScanSettings: 能夠配置掃描設置,採用構造器模式。能夠設置
等,具體能夠查看源代碼或者Google官方文檔。
BluetoothAdapter.LeScanCallback: 是掃描結果回調,第一個參數是表明藍牙設備的類;第二個參數是藍牙的信號強弱指標,經過藍牙的信號指標,咱們能夠大概計算出藍牙設備離手機的距離。計算公式爲:d = 10^((abs(RSSI) - A) / (10 * n));第三個參數是藍牙廣播出來的廣播數據。
ScanCallback: 是startScan的掃描回調,自己是一個抽象類;有三個方法可選實現方法;
//掃描結束是返回全部匹配的設備列表
void onBatchScanResults(List<ScanResult> results)
//當發現設備廣播時回調,第一個參數是回調類型即前邊說的ScanSettings中設置的回調類型,第二個參數是掃描到的藍牙設備信息
void onScanResult(int callbackType, ScanResult result)
//當啓動掃描失敗時調用該方法
void onScanFailed(int errorCode)
複製代碼
而後就能夠調用startLeScan或者startScan開始搜索設備了。若想中止掃描須要根據開始掃描方法肯定調用stopLeScan或者stopScan;出如今回調中的設備會重複出現,因此須要手動過濾掉已經發現的設備。另外須要強調的是終止掃描時傳入的回調必須是開始掃描時傳入的回調,不然掃描動做不會中止。
鏈接藍牙設備能夠經過 BluetoothDevice#ConnectGatt 方法鏈接,也能夠經過 BluetoothGatt#connect 方法進行從新鏈接。
//BluetoothDevice#connectGatt
//第二個參數表明是否須要自動鏈接,true-表示若是設備斷開了,會不斷的嘗試自動鏈接,false-表示只進行一次鏈接嘗試
BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)
複製代碼
當調用藍牙的鏈接方法以後,藍牙會異步執行藍牙鏈接的操做,若是鏈接成功會回調 BluetoothGattCalbackl#onConnectionStateChange 方法。這個方法運行的線程是一個 Binder 線程,因此不建議直接在這個線程處理耗時的任務,由於這可能致使藍牙相關的線程被阻塞。
void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
複製代碼
這一個方法有三個參數,第一個就藍牙設備的Gatt服務鏈接類。第二個參數表明是否成功執行了鏈接操做,若是爲BluetoothGatt.GATT_SUCCESS表示成功執行鏈接操做,第三個參數纔有效,不然說明此次鏈接嘗試不成功。有時候,咱們會遇到 status == 133 的狀況,根據網上大部分人的說法,這是由於Android最多支持鏈接6到7個左右的藍牙設備,若是超出了這個數量就沒法再鏈接了。因此當咱們斷開藍牙設備的鏈接時,還必須調用 BluetoothGatt#close方法釋放鏈接資源。不然,在屢次嘗試鏈接藍牙設備以後很快就會超出這一個限制,致使出現這一個錯誤再也沒法鏈接藍牙設備。 第三個參數表明當前設備的鏈接狀態,若是newState==BluetoothProfile.STATE_CONNECTED說明設備已經鏈接,能夠進行下一步的操做了(發現藍牙服務,也就是Service)。當藍牙設備斷開鏈接時,這一個方法也會被回調,其中的newState==BluetoothProfile.STATE_DISCONNECTED。
在成功鏈接到藍牙設備以後才能進行這一個步驟,也就是說在BluetoothGattCalbackl#onConnectionStateChang方法被成功回調且表示成功鏈接以後調用BluetoothGatt#discoverService 這一個方法。當這一個方法被調用以後,系統會異步執行發現服務的過程,直到 BluetoothGattCallback#onServicesDiscovered被系統回調以後,手機設備和藍牙設備纔算是真正創建了可通訊的鏈接。
到這一步,咱們已經成功和藍牙設備創建了可通訊的鏈接,接下來就能夠執行相應的藍牙通訊操做了,例如寫入數據,讀取藍牙設備的數據等等。
當咱們發現服務以後就能夠經過BluetoothGatt#getService獲取BluetoothGattService,接着經過 BluetoothGattService#getCharactristic獲取BluetoothGattCharactristic。經過BluetoothGattCharactristic#readCharacteristic方法能夠通知系統去讀取特定的數據。若是系統讀取到了藍牙設備發送過來的數據就會調用BluetoothGattCallback#onCharacteristicRead方法。經過BluetoothGattCharacteristic#getValue能夠讀取到藍牙設備的數據。
和讀取數據同樣,在執行寫入數據前須要獲取到BluetoothGattCharactristic。接着執行如下步驟:
BLE app一般須要獲取設備中characteristic 變化的通知。以下爲一個Characteristic設置一個監聽:
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
//其中的參數爲00002902-0000-1000-8000-00805f9b34fb
BluetoothGattDescriptor descriptor=characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
複製代碼
以上代碼,除了經過 BluetoothGatt#setCharacteristicNotification開啓Android端接收通知的開關,還須要往Characteristic的Descriptor屬性寫入開啓通知的數據開關使得當硬件的數據改變時,主動往手機發送數據。
藍牙接收數據的特性和藍牙發送通知數據的特性不能相同,不然在高版本系統上沒法接收到通知的數據
當咱們鏈接藍牙設備完成一系列的藍牙操做以後就能夠斷開藍牙設備的鏈接了。經過 BluetoothGatt#disconnect能夠斷開正在鏈接的藍牙設備。當這一個方法被調用以後,系統會異步回調 BluetoothGattCallback#onConnectionStateChange方法。經過這個方法的 newState 參數能夠判斷是鏈接成功仍是斷開成功的回調。
因爲 Android 藍牙鏈接設備的資源有限,當咱們執行斷開藍牙操做以後必須執行 BluetoothGatt#close 方法釋放資源。須要注意的是經過 BluetoothGatt#close 方法也能夠執行斷開藍牙的操做,不過BluetoothGattCallback#onConnectionStateChange 將不會收到任何回調。此時若是執行 BluetoothGatt#connect方法會獲得一個藍牙 API 的空指針異常。因此,咱們推薦的寫法是當藍牙成功鏈接以後,經過BluetoothGatt#disconnect 斷開藍牙的鏈接,緊接着在BluetoothGattCallback#onConnectionStateChange 執行 BluetoothGatt#close 方法釋放資源。