安卓 BLE 開發(fā)詳解


相關(guān)概念

  • BR
    Basic Rate,早期的傳統(tǒng)藍(lán)牙技術(shù) V1.1, V1.2 版本,傳輸速率為748~810kb/s。

  • EDR
    Enhanced Data Rate,傳統(tǒng)藍(lán)牙技術(shù) V2.0, V2.1 版本,優(yōu)化傳輸速率,減少耗電,速率為1.8M/s~2.1M/s。

  • AMP
    GenericAlternate MAC/PHY,高速藍(lán)牙技術(shù),V3.0版本。
    采用交替射頻技術(shù),藍(lán)牙模塊僅創(chuàng)建設(shè)備間的配對,數(shù)據(jù)傳輸通過WIFI射頻來完成以達(dá)到高速率。
    假如設(shè)備某一方?jīng)]有內(nèi)建WIFI模塊,速率將降至 EDR 速率。

  • BLE
    Bluetooth Low Energy,低耗藍(lán)牙技術(shù),V4.0版本的新規(guī)范,通過三個方式實(shí)現(xiàn)超低功耗:
    1.大幅度削減掃描信道
    2.極短的鏈路連接時間
    3.采用長度很短的數(shù)據(jù)包
    低耗藍(lán)牙的芯片有單模和雙模,前者只支持LE技術(shù),后者兼容BR/EDR技術(shù)。


1:GATT 協(xié)議

  • GATT概述
    GATT(Generic Attributes,通用屬性協(xié)議),定義了一種面向 BLE設(shè)備 的分層數(shù)據(jù)結(jié)構(gòu)。
    GATT建立在ATT( Attribute Protocol,通用訪問協(xié)議)之上,ATT使用GATT數(shù)據(jù)定義兩個BLE設(shè)備間收發(fā)標(biāo)準(zhǔn)消息的方式。
    由于 GATT 是面向 LE 技術(shù)的協(xié)議,所以在只支持 BR/EDR 技術(shù)的設(shè)備上無法使用。

  • GATT分層數(shù)據(jù)結(jié)構(gòu)的層次

    GATT定義了用于BLE設(shè)備傳輸數(shù)據(jù)的標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu),結(jié)構(gòu)主要包括了如上圖所示的:
    1.服務(wù)(Service)
    2.特征(Characteristic)
    3.描述符(Descriptor)。

  • 配置文件(Profile)
    配置文件,GATT頂層,該由滿足 配置實(shí)例 需要的一個或多個服務(wù)組成。

  • 服務(wù)(Service)
    服務(wù) 由 特征 和 其他服務(wù)的引用 組成,擁有固定的 UUID 作為標(biāo)記值。
    設(shè)備的功能主要體現(xiàn)在服務(wù)上,每種服務(wù)都對應(yīng)著某一種功能。

    可以到官網(wǎng)上查看服務(wù)列表 GATT Services
    通過服務(wù)列表中的 Assigned Numbers 可以獲取服務(wù)的UUID。

    Assigned Numbers轉(zhuǎn)換成可用的服務(wù)UUID 的方法于文檔 Service Discovery
    簡單來說,就是:

      "服務(wù)的Assigned Numbers"-0000-1000-8000-00805F9B34FB
    
  • 特征(Characteristic)
    特征是BLE通信的主體,是一個服務(wù)端和客戶端共享的讀寫空間。
    主機(jī)在從機(jī)上獲取所需的信息,實(shí)際就是通過獲取對應(yīng)的特征的內(nèi)容進(jìn)行的。

    特征由屬性值和描述符組成:

    1. 屬性值
      屬性值包括聲明(Declaration),值(Value),一個屬性值最少包括一個聲明和一個值,即是屬性值是特征必選的條目。
    2. 描述符
      特征可以包括零到若干個描述符,可選條目。

    特征信息列表可以查看官方文檔 GATT Characteristics

  • 描述符(Descriptors)
    用于表達(dá) 特征 的其他附加信息,如特征值的有效范圍,可讀性描述等信息。

    其中包含了特殊的 CCCD(Client Characteristic Configuration Descriptor, Assigned Number : 0x2902):
    CCCD 可以設(shè)置 服務(wù)端 在對應(yīng)特征值發(fā)生變化時,是否對 客戶端 進(jìn)行信息 推送(直接發(fā)送信息) 或 提示(發(fā)送一個提示并等待回復(fù))。
    當(dāng)特征包含通知能力時,CCCD為必選項。

    描述符列表可以查看官方文檔 GATT Descriptors


2:Android BLE 相關(guān) API

  • BluetoothAdapter
    藍(lán)牙適配器:

    本地設(shè)備藍(lán)牙適配器,提供基本藍(lán)牙功能的工具,例如開啟藍(lán)牙發(fā)現(xiàn),查詢配對設(shè)備,實(shí)例化藍(lán)牙設(shè)備鏈接,監(jiān)聽連接請求,掃描設(shè)備等。
    基本上說,藍(lán)牙適配器是進(jìn)行藍(lán)牙操作的起點(diǎn)。

    獲取BluetoothAdapter實(shí)例,在 API 18 及以上的設(shè)備,使用:

    BluetoothManager.getAdapter
    

    在API18以下設(shè)備使用以下API獲取:

    BluetoothAdapter.getDefaultAdapte
    

    本類線程安全。涉及到的權(quán)限為:

    <uses-permission android:name="android.permission.BLUETOOTH"/> 
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    
  • BluetoothDevice
    遠(yuǎn)程藍(lán)牙設(shè)備:

    提供了遠(yuǎn)程藍(lán)牙設(shè)備的基本信息,如名稱,地址,類別,綁定狀態(tài)等。
    本質(zhì)上只是對藍(lán)牙硬件地址的簡單包裝。該類的實(shí)例不可修改。

    一般來說,通過掃描設(shè)備的掃描結(jié)果回調(diào)中獲取。
    也可以直接通過以下方式獲取:

    /* 使用已知的物理地址作為參數(shù)進(jìn)行連接 */
    BluetoothAdapter.getRemoteDevice(address);
    /* 獲取已適配的藍(lán)牙記錄列表 */
    BluetoothAdapter.getBondedDevices();
    
  • BluetoothGatt
    GATT客戶端,GATT協(xié)議的公共API,提供了GATT的基本功能,如實(shí)現(xiàn)藍(lán)牙設(shè)備的通信。
    通過掃描支持LE技術(shù)的藍(lán)牙設(shè)備,獲取到 BluetoothDevice,然后通過:

    /* GATT連接操作的回調(diào) */
    BluetoothGattCallback mCallback;
    BluetoothDevice.connectGatt(content, autoConnect, mCallback);
    

    通過設(shè)置 BluetoothGattCallback 回調(diào),可以從回調(diào)中得到 BluetoothGatt 實(shí)例。

  • BluetoothGattCallback
    GATT狀態(tài)回調(diào),大部分GATT操作的結(jié)果都會通過該類實(shí)例回調(diào),包括:

    /* 連接狀態(tài)回調(diào),包括連接到服務(wù)器 / 從服務(wù)器斷開連接 */
    onConnectionStateChange();
    /* 遠(yuǎn)程設(shè)備發(fā)現(xiàn)新服務(wù) */
    onServicesDiscovered();
    /* 特征相關(guān)操作的回調(diào) */
    onCharacteristicRead();
    onCharacteristicWrite();
    onCharacteristicChanged();
    

    同時,掃描設(shè)備 和 停止掃描 的操作,都需要用到該類的實(shí)例。

  • BluetoothGattService
    GATT服務(wù),根據(jù)服務(wù)的 UUID,嘗試獲取服務(wù)實(shí)例。

    /* 如果對應(yīng)的設(shè)備支持該服務(wù),則返回一個服務(wù)的實(shí)例,否則返回空 */
    BluetoothGatt.getService(uuid); 
    
  • BluetoothGattCharacteristic
    GATT特征,實(shí)際通信中的數(shù)據(jù)信息主體。通過以下方法獲取:

    /* 獲取對應(yīng)UUID的特征 */
    BluetoothGattService.getCharacteristic(uuid);
    /* 獲取服務(wù)的特征列表 */
    BluetoothGattService.getCharacteristics();
    

3:Android BLE 開發(fā)示例

  • 聲明權(quán)限

    一個聲明和兩個基本權(quán)限:

    <uses-feature android:name"android.permission.BLUETOOTH_ADMIN"/>
    
    <uses-permission android:name="android.permission.BLUETOOTH"/> 
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> 
    

    執(zhí)行搜索BLE設(shè)備的時候,需要使用定位權(quán)限。
    而在5.0及以上的版本,需要手動聲明GPS硬件模塊功能的權(quán)限:

    <uses-feature android:name="android.hardware.location.gps"/>
    

    而在6.0及以上版本,掃描設(shè)備還需要 動態(tài)申請 以下權(quán)限:

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    
  • 檢查設(shè)備支持性
    如果設(shè)備不支持BLE,可以跳過BLE相關(guān)操作了。

    boolean checkSupport() {
        return getPackageManager()
                  .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
    }
    
  • 初始化BluetoothAdapter

    private BluetoothAdapter mAdapter;
    
    BluetoothManager bluetoothManager = 
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    mAdapter = bluetoothManager.getAdapter();
    

    然后檢查藍(lán)牙的支持性,及是否已打開藍(lán)牙。

    if (mAdapter == null) {
        return;
    }
    
    ... private final static int REQUEST_ENABLE_BT = 1;
    
    if (!mAdapter.isEnabled()) {
        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(intent, REQUEST_ENABLE_BT);
    }
    
  • 啟動設(shè)備掃描

    創(chuàng)建LeScanCallback實(shí)例:
    首先需要實(shí)現(xiàn)一個 LeScanCallback 實(shí)例,掃描結(jié)果會通過實(shí)例的 onLeScan 方法返回:

    LeScanCallback mCallBack = new LeScanCallback (){ 
         @Override
         public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {}
    }
    

    啟動掃描與停止掃描:

    ··· static int SCAN_TIME = 5_000;
    ··· Handler mHandler = new Handler();
    
    /* 開始掃描:
       由于掃描消耗電量,所以不能一直處于掃描狀態(tài),
       設(shè)置掃描一段時間后關(guān)閉掃描 */
    mAdapter.startLeScan(mCallBack);
    mHandler.postDelay(()->{
    
       /* 關(guān)閉掃描:
        * 注意需要傳入啟動掃描時的 callback對象,否則無效 */
       mAdapter.stopLeScan(mCallBack);
    }, SCAN_TIME);
    

    API 21 及以上時,掃描操作應(yīng)使用 BluetoothLeScanner

    final ScanCallback callback = new ScanCallback() {};
    final BluetoothLeScanner scanner = mAdapter.getBluetoothLeScanner();
    
    scanner.startScan(new ScanCallback(){});
    mHandler.postDelay(()->{
       scanner.stopScan(scanCallback);
    }, SCAN_TIME);
    
  • 獲取掃描結(jié)果
    以 LeScanCallback 的回調(diào)方法 onLeScan 分析:

     /**
      * @param device:    識別到的遠(yuǎn)程設(shè)備
      *
      * @param rssi:      信號強(qiáng)度指示,計數(shù)為dB。可以通過:
                           d = 10^((abs(RSSI) - A) / (10 * n)) 
                           計算出距離。A和n根據(jù)環(huán)境改變,需經(jīng)實(shí)驗測出,
                           給出兩個網(wǎng)上的經(jīng)驗值:
                           <1>  A: 50 n: 2.5    <2>  A: 59 n: 2.0
      *
      * @param scanRecord:廣播數(shù)據(jù)和掃描應(yīng)答數(shù)據(jù)數(shù)據(jù)
                           BLE設(shè)備在對外廣播中,廣播中會攜帶一些有用的信息。
                           其中包含了 廣播數(shù)據(jù) 和 掃描應(yīng)答數(shù)據(jù),
                           兩者有效荷載最大都為 31字節(jié)(藍(lán)牙4),
                           以十六進(jìn)制格式存儲,可通過 bytesToHex 轉(zhuǎn)換成可用的字符串。
      */
     void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {}
    

    注意相同的 BluetoothDevice 會重復(fù)出現(xiàn)在回調(diào)中,所以如果要記錄藍(lán)牙列表,需要自行 過濾 重復(fù)出現(xiàn)的設(shè)備,或更新對應(yīng)重復(fù)出現(xiàn)的設(shè)備的信息。
    bytesToHex 參考

  • 連接外圍設(shè)備
    通過 BluetoothDevice 的 connectGatt 方法獲取一個 BluetoothGatt 實(shí)例。
    connectGatt 有多個重載方法,這里介紹其中最復(fù)雜的重載方法:

     /**
      * 以客戶端的身份連接到該設(shè)備托管的GATT服務(wù)器
      *
      * @param autoConnect:自動連接,設(shè)備不可用時會不斷嘗試重連。
      *
      * @param callback:   BluetoothGattCallback實(shí)例,用于接收異步回調(diào)
      *
      * @param transport:  GATT連接到雙模設(shè)備的首選傳輸模式:
      *                     1:TRANSPORT_AUTO   自動選擇 (默認(rèn)值)
      *                     2:TRANSPORT_BREDR  BR/EDR 傳統(tǒng)藍(lán)牙
      *                     3:TRANSPORT_LE     LE 低耗藍(lán)牙
      *
      * @param phy:        PHY物理層的模式選擇:
      *                     1:PHY_LE_1M_MASK:
      *                        默認(rèn)值,LE設(shè)備強(qiáng)制要求支持的模式,
      *                        符號速率為1M/s,未編碼。
      *                     2:PHY_LE_2M_MASK:
      *                        符號速率為2M/s,未編碼,
      *                        用于 藍(lán)牙5 的 "2x speed" 2倍速率。
      *                     3:PHY_LE_CODED_MASK:
      *                        在數(shù)據(jù)包中增加糾錯編碼以實(shí)現(xiàn)更遠(yuǎn)的傳輸范圍,
      *                        以實(shí)現(xiàn) 藍(lán)牙5 的 "4x range" 4倍范圍。
      *                        使用FEC編碼,根據(jù)方案又分為:
      *                        LE Coded S=2:2個編碼位代替原來一個數(shù)據(jù)位,
      *                                      速率降為 500K/s,傳輸范圍增大2倍;
      *                        LE Coded S=8:8個編碼位代替原來一個數(shù)據(jù)位,
      *                                      速率降為 125K/s,傳輸范圍增大4倍;
      *                     設(shè)置 autoConnect 自動連接時,該項無效
      *
      * @param handler:    傳入一個Handler,以指定回調(diào)發(fā)生的線程,
      *                     傳入null時,回調(diào)將會在一個未指定的后臺線程上進(jìn)行。
      */
     BluetoothGatt connectGatt(Context context,
                               boolean autoConnect,
                               BluetoothGattCallback callback, 
                               int transport, int phy,
                               Handler handler) { ··· }
    

    一般情況下使用默認(rèn)值既可,
    注意必須傳入非空的callback,否則會拋出 IllegalArgumentException

    BluetoothDevice.connectGatt(content, autoConnect, callback);
    

    當(dāng)連接成功時,會回調(diào) callback 的 onConnectionStateChange 方法

    /**
     * GATT客戶端的連接狀態(tài)回調(diào)
     *
     * @param gatt:    GATT客戶端。
     * @param status:  連接或斷開操作的執(zhí)行結(jié)果, 成功返回 GATT_SUCCESS
     * @param newState:當(dāng)前的連接狀態(tài):STATE_CONNECTED / STATE_DISCONNECTED
     */
    void onConnectionStateChange(BluetoothGatt gatt, int status, int newState);
    

    status 表示連接操作的結(jié)果,只有status為 GATT_SUCCESS 時,newState才是有效值。

    注意一臺安卓設(shè)備最多同時連接6個左右的藍(lán)牙設(shè)備,超出時可能出現(xiàn):
    status == 133 連接錯誤,
    所以需要注意調(diào)用 BluetoothGatt.close() 方法進(jìn)行資源釋放。
    可參考:Android中BLE連接出現(xiàn)“BluetoothGatt status 133”的解決方法

    當(dāng) status == GATT_SUCCESS,且 newState == STATE_CONNECTED 時,表示已成功連接設(shè)備,可以進(jìn)行下一步操作。

  • 發(fā)現(xiàn)服務(wù)
    在建立連接之后,就可以通過 BluetoothGatt實(shí)例 進(jìn)行發(fā)現(xiàn)服務(wù)操作,查找設(shè)備支持的服務(wù)。

    /**
     * 異步操作,發(fā)現(xiàn)服務(wù)完成時,會回調(diào)onServicesDiscovered()方法。
     * 假如發(fā)現(xiàn)服務(wù)已在啟動狀態(tài)中,則返回true
     */
    boolean discoverService();
    

    等待 BluetoothGattCallback 的 onServicesDiscovered() 被回調(diào):

    /**
     * @param gatt:   執(zhí)行發(fā)現(xiàn)服務(wù)后的GATT客戶端。
     * @param status: 發(fā)現(xiàn)服務(wù)的執(zhí)行結(jié)果, 成功返回 GATT_SUCCESS
     */
    void onServicesDiscovered(BluetoothGatt gatt, int status) ;
    

    當(dāng) status 返回GATT_SUCCESS,表示與外部設(shè)備成功建立 可通信連接
    意味著可以執(zhí)行如:寫入數(shù)據(jù),讀取藍(lán)牙設(shè)備的數(shù)據(jù)等 藍(lán)牙通信操作了。
    先把獲取到的 BluetoothGatt實(shí)例 記錄為 mGatt:

    ··· BluetoothGatt mGatt;
    
    void onServicesDiscovered(BluetoothGatt gatt, int status) {
        mGatt = gatt;
    }
    
  • 獲取服務(wù)
    發(fā)現(xiàn)服務(wù)成功之后,可以通過以下的方法嘗試獲取 BluetoothGattService 實(shí)例:

    /* 獲取遠(yuǎn)程設(shè)備提供的服務(wù)列表,
     * 如果未執(zhí)行發(fā)現(xiàn)服務(wù),會返回一個空列表 */
    mGatt.getServices();
    
    /* 通過服務(wù)的UUID,獲取指定的服務(wù),
     * 如果遠(yuǎn)程設(shè)備不支持給定UUID的服務(wù),返回null,
     * 如果遠(yuǎn)程設(shè)備存在多個給定UUID的服務(wù)實(shí)例,則返回第一個實(shí)例 */
    mGatt.getService(UUID);
    

    獲取到 BluetoothGattService 之后,就可以通過獲取服務(wù)的特征進(jìn)行讀寫。

  • 特征的讀寫數(shù)據(jù)
    前面介紹了,通信主體實(shí)際上是 特征,要進(jìn)行讀寫操作,其實(shí)就是在操作特征里的屬性詞條,所以要先通過 服務(wù) 獲取 特征:

    /* 假設(shè) service 是從上一步獲取到的一個 BluetoothGattService 實(shí)例*/
    ··· BluetoothGattService service;
    
    /* 獲取該服務(wù)的特征列表 */
    service.getCharacteristics();
    
    /* 通過特征的UUID,獲取指定的特征,
     * 如果沒有找到給定UUID的特征,返回null,
     * 如果服務(wù)中存在多個給定UUID的特征,則返回第一個實(shí)例 */
    service.getCharacteristic(UUID);
    

    獲取到了特征之后,就可以通過上面獲取到的 mGatt 讀寫信息:

    /* 上一步獲取的 BluetoothGattCharacteristic 實(shí)例 */
    ··· BluetoothGattCharacteristic characteristic;
    
    /* 從關(guān)聯(lián)的遠(yuǎn)程設(shè)備讀取請求的特征,
     * 異步操作,請求發(fā)起成功則返回true,讀取完成會回調(diào):
     * BluetoothGattCallback.onCharacteristicRead() */
    mGatt.readCharacteristic(characteristic);
    
    /* 將給定的特征及其值寫入關(guān)聯(lián)的遠(yuǎn)程設(shè)備,
     * 異步操作,請求發(fā)起成功則返回true,寫入完成會回調(diào):
     * BluetoothGattCallback.onCharacteristicWrite() */
    mGatt.writeCharacteristic(characteristic);
    

    讀寫操作都是異步操作,方法返回的是請求是否成功,請求結(jié)果都會回調(diào) BluetoothGattCallback 的方法:

    /**
     * 讀操作的回調(diào)
     * @param characteristic:  讀取后的特征
     * @param status:          讀取結(jié)果,成功為 GATT_SUCCESS
     */
    void onCharacteristicRead(BluetoothGatt gatt, 
                              BluetoothGattCharacteristic characteristic,
                              int status) { ··· }
    
    /**
     * 寫操作的回調(diào)
     * @param characteristic:  寫入后的特征
     *                          注意:這里返回的特征,為設(shè)備當(dāng)前的特征, 
     *                          應(yīng)該在該回調(diào)中,應(yīng)對比該特征的內(nèi)容是否符合期望值,
     *                          如果與期望值不同,應(yīng)該選擇重發(fā)或終止寫入。                    
     * 
     * @param status:          寫入結(jié)果,成功為 GATT_SUCCESS
     */
    void onCharacteristicWrite(BluetoothGatt gatt, 
                              BluetoothGattCharacteristic characteristic,
                              int status) { ··· }
    

    寫數(shù)據(jù)的時候要注意,需要對比返回的特征和寫入的特征,判斷是否寫入成功或者產(chǎn)生了異常,選擇繼續(xù)寫入或者重寫,或者放棄操作。

  • 描述符的讀寫數(shù)據(jù)
    讀寫方式與 特征 的 讀寫方式基本一致,不再過多描述 :

    /* 獲取描述符 */
    ··· BluetoothGattCharacteristic characteristic;
    characteristic.getDescriptors();
    characteristic.getDescriptor(UUID);
    
    /* 通過 mGatt 讀寫數(shù)據(jù)
     * 同樣,寫操作需要做寫入結(jié)果校驗 */
    ··· BluetoothGattDescriptor descriptor;
    mGatt.readDescriptor(descriptor);
    mGatt.writeDescriptor(descriptor);
    
    /* 結(jié)果回調(diào) */
    void onDescriptorRead(BluetoothGatt gatt,
                           BluetoothGattDescriptor descriptor,
                           int status) { ··· }
    void onDescriptorWrite(BluetoothGatt gatt,
                           BluetoothGattDescriptor descriptor,
                           int status) { ··· }
    
  • 讀寫數(shù)據(jù)需要注意的問題

    寫入數(shù)據(jù)量:
    每次寫操作的時候,無論是 特征 或者 描述符,一般來說最大只能設(shè)置 20個字節(jié) 的數(shù)據(jù)。
    這是因為ATT協(xié)議中,最大傳輸單元MTU的默認(rèn)大小為23字節(jié),其中3字節(jié)用于ATT協(xié)議的控制數(shù)據(jù),所以GATT可用的數(shù)據(jù)大小默認(rèn)為剩余的20字節(jié)。

    ATT的MTU最大值為512,在API 21及以上的安卓平臺,可以通過以下方法嘗試改變MTU的大小:

    ··· int mMtu;
    
    /* 請求變更MTU的大小 */
    BluetoothGatt.requestMtu(mMtu);
    
    /* 請求結(jié)果通過 BluetoothGattCallback 回調(diào) 
     * 當(dāng)statue返回為 GATT_SUCCESS 時,表示變更成功
     * 變更成功后,可以使用(mMtu - 3)的大小傳輸數(shù)據(jù)*/
    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {}
    

    無法改變的時候,超過20字節(jié)的數(shù)據(jù),進(jìn)行分包發(fā)送(BLE服務(wù)端需要支持)。

    讀寫間隔:
    讀寫操作都是隊列操作,需要等待操作結(jié)果返回后,才能進(jìn)行下次操作,若當(dāng)次操作未完成,下次操作調(diào)用時,將直接返回操作啟用失敗。

    寫入操作時,需等待服務(wù)器的確認(rèn)信息,即寫入回調(diào),再進(jìn)行下次寫入操作。
    當(dāng)寫入類型設(shè)置為 不需要接收服務(wù)器確認(rèn)信息(PROPERTY_WRITE_NO_RESPONSE)以加快傳輸速度時,兩次操作之間應(yīng)保留 80ms ~ 100ms 或以上的延時。

  • 數(shù)據(jù)變更通知
    前面說到ATT支持通知,一些特征在值發(fā)生變化時,可以主動向申請了監(jiān)聽數(shù)據(jù)變化的客戶端推送通知或指示(不帶數(shù)據(jù))。
    開啟特征的監(jiān)聽,需要進(jìn)行兩步操作:

    設(shè)置特征信息推送

    /**
     * 啟用或禁用給定特征的通知或指示
     * @param characteristic:  需要進(jìn)行操作的特征
     * @param enable :         開啟或關(guān)閉
     */
    BluetoothGatt.setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
                                                boolean enable);
    

    寫入CCCD
    雖然開啟了特征的信息推送,但假如特征本身禁用了通知和指示,則不會有更新推送。
    前面提到了一個特殊的標(biāo)識符CCCD,用于控制特征的消息推送。需要對特征的CCCD描述符進(jìn)行操作,將其值置為 1 / 2,才能開啟對應(yīng)的 通知 / 指示 功能。

    /* 設(shè)置特征信息推送 */
    ··· BluetoothGattCharacteristic characteristic;
        mGatt.setCharacteristicNotification(characteristic,true);
    
    /* CCCD 的UUID */
    private UUID ID_CCCD = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");  
    
    /* 獲取CCCD */
    BluetoothGattDescriptor cccd = characteristic.getDescriptor(ID_CCCD);
    
    /* 設(shè)置推送通知,參考值為:
     * BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE:   通知
     * BluetoothGattDescriptor.ENABLE_INDICATION_VALUE:     指示
     * BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE:  關(guān)閉
     */  
    cccd.setValue(參考值);
    /* 寫入CCCD */
    mGatt.writeDescriptor(descriptor);
    

    以上操作完成后,即開啟對應(yīng)特征的更新推送了。

    接收推送
    更新推送會回調(diào)BluetoothGattCallback的onCharacteristicChanged()方法:

    /**
     * 特征變更推送觸發(fā)的回調(diào)
     * @param gatt:            特征 關(guān)聯(lián)的 BluetoothGatt 實(shí)例
     * @param characteristic:  更新后的 特征
     */
    void onCharacteristicChanged(BluetoothGatt gatt,
                                 BluetoothGattCharacteristic characteristic)
    
  • 關(guān)閉客戶端
    用完的東西總是要收拾好。

    斷開連接:

    /* 斷開當(dāng)前連接,如果正在連接中,則取消連接操作 */
    BluetoothGatt.disconnect();
    

    斷開連接操作后,結(jié)果回調(diào) onConnectionStateChange() 方法,應(yīng)該通過回調(diào)返回的結(jié)果 status 和 newState 判斷是否成功斷開。

    關(guān)閉Gatt客戶端:
    成功斷開連接之后(甚至是斷開失敗),應(yīng)該調(diào)用 BluetoothGatt 的close() 方法關(guān)閉客戶端釋放資源。
    安卓同時連接遠(yuǎn)程設(shè)備的資源極其有限,在所以任何情況不再需要連接遠(yuǎn)程設(shè)備時,都要使用BluetoothGatt 的 close() 方法釋放資源。


參考文章:
藍(lán)牙技術(shù)基礎(chǔ)知識學(xué)習(xí)
藍(lán)牙核心技術(shù)概述
GATT協(xié)議及藍(lán)牙核心系統(tǒng)結(jié)構(gòu)
Android BLE的總結(jié)
Android BLE 藍(lán)牙開發(fā)入門

更具體的藍(lán)牙技術(shù)說明請查看官方網(wǎng)站
Bluetooth Technology Website

歡迎留言,歡迎關(guān)注,會持續(xù)更新 安卓開發(fā) 中遇到的問題和技術(shù)上的一些自我總結(jié)。
如有錯誤,歡迎指出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內(nèi)容

  • 因為自己的項目中有用到了藍(lán)牙相關(guān)的功能,所以之前也斷斷續(xù)續(xù)地針對藍(lán)牙通信尤其是BLE通信進(jìn)行了一番探索,整理出了一...
    陳利健閱讀 115,781評論 172 295
  • BLE 與經(jīng)典藍(lán)牙的區(qū)別 BLE 的 Kotlin 下實(shí)踐 BluetoothGattCallback 不回調(diào)異常...
    chauI閱讀 11,110評論 1 7
  • 初識低功耗藍(lán)牙 Android 4.3(API Level 18)開始引入Bluetooth Low Energy...
    JBD閱讀 112,854評論 46 342
  • 我聽到爆竹的雷動砰砰砰磅磅磅在大年初六在京郊大地的夜晚 響得高亢響得透亮響得正義凜然 聽得我心虛聽得鬼心虛聽得人類...
    藍(lán)柿閱讀 278評論 7 1
  • 又來到這棵老樹下,密密蓬蓬的枝丫向四周撐開披撒著,滿目的蔥綠中綴滿了粉色的花,每朵花都像是一團(tuán)小火焰,細(xì)細(xì)的花絲嬌...
    古董碎片閱讀 479評論 1 5