Android BLE開(kāi)發(fā)詳解和FastBle源碼解析

因?yàn)樽约旱捻?xiàng)目中有用到了藍(lán)牙相關(guān)的功能,所以之前也斷斷續(xù)續(xù)地針對(duì)藍(lán)牙通信尤其是BLE通信進(jìn)行了一番探索,整理出了一個(gè)開(kāi)源框架FastBle與各位分享經(jīng)驗(yàn)影钉。
源碼地址:

https://github.com/Jasonchenlijian/FastBle

隨著對(duì)FastBle框架關(guān)注的人越來(lái)越多牛郑,與我討論問(wèn)題的小伙伴也多起來(lái)羔挡,所以整理了一篇文章纵顾,詳細(xì)介紹一下框架的用法伍茄,一些坑,還有我對(duì)Android BLE開(kāi)發(fā)實(shí)踐方面的理解施逾。

文章分為3個(gè)部分:

  • FastBle的使用
  • BLE開(kāi)發(fā)實(shí)踐方面的理解
  • FastBle源碼解析

1. FastBle的使用

1.1 聲明權(quán)限

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  • android.permission.BLUETOOTH : 這個(gè)權(quán)限允許程序連接到已配對(duì)的藍(lán)牙設(shè)備, 請(qǐng)求連接/接收連接/傳輸數(shù)據(jù)需要改權(quán)限, 主要用于對(duì)配對(duì)后進(jìn)行操作;
  • android.permission.BLUETOOTH_ADMIN : 這個(gè)權(quán)限允許程序發(fā)現(xiàn)和配對(duì)藍(lán)牙設(shè)備, 該權(quán)限用來(lái)管理藍(lán)牙設(shè)備, 有了這個(gè)權(quán)限, 應(yīng)用才能使用本機(jī)的藍(lán)牙設(shè)備, 主要用于對(duì)配對(duì)前的操作;
  • android.permission.ACCESS_COARSE_LOCATION和android.permission.ACCESS_FINE_LOCATION:Android 6.0以后敷矫,這兩個(gè)權(quán)限是必須的,藍(lán)牙掃描周圍的設(shè)備需要獲取模糊的位置信息汉额。這兩個(gè)權(quán)限屬于同一組危險(xiǎn)權(quán)限曹仗,在清單文件中聲明之后,還需要再運(yùn)行時(shí)動(dòng)態(tài)獲取闷愤。

1.2. 初始化及配置

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    BleManager.getInstance().init(getApplication());
    BleManager.getInstance()
            .enableLog(true)
            .setReConnectCount(1, 5000)
            .setOperateTimeout(5000);
}

在使用之前整葡,需要事先調(diào)用初始化init(Application app)方法件余。此外讥脐,可以進(jìn)行一些自定義的配置,比如是否顯示框架內(nèi)部日志啼器,重連次數(shù)和重連時(shí)間間隔旬渠,以及操作超時(shí)時(shí)間。

1.3. 掃描外圍設(shè)備

APP作為中心設(shè)備端壳,想要與外圍硬件設(shè)備建立藍(lán)牙通信的前提是首先得到設(shè)備對(duì)象告丢,途徑是掃描。在調(diào)用掃描方法之前损谦,你首先應(yīng)該先處理下面的準(zhǔn)備工作岖免。

  • 判斷當(dāng)前Android設(shè)備是否支持BLE。
    Android 4.3以后系統(tǒng)中加入了藍(lán)牙BLE的功能照捡。

     BleManager.getInstance().isSupportBle();
    
  • 判斷當(dāng)前Android設(shè)備的藍(lán)牙是否已經(jīng)打開(kāi)颅湘。
    可以直接調(diào)用下面的判斷方法來(lái)判斷本機(jī)是否已經(jīng)打開(kāi)了藍(lán)牙,如果沒(méi)有栗精,向用戶拋出提示闯参。

    BleManager.getInstance().isBlueEnable();
    
  • 主動(dòng)打開(kāi)藍(lán)牙。
    除了判斷藍(lán)牙是否打開(kāi)給以用戶提示之外悲立,我們也可以通過(guò)程序直接幫助用戶打開(kāi)藍(lán)牙開(kāi)關(guān)鹿寨,打開(kāi)方式有這幾種:
    方法1:通過(guò)藍(lán)牙適配器直接打開(kāi)藍(lán)牙。

    BleManager.getInstance().enableBluetooth();
    

    方法2:通過(guò)startActivityForResult引導(dǎo)界面引導(dǎo)用戶打開(kāi)藍(lán)牙薪夕。

    Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(intent, 0x01);
    

    需要注意的是脚草,第一種方法是異步的,打開(kāi)藍(lán)牙需要一段時(shí)間原献,調(diào)用此方法后馏慨,藍(lán)牙不會(huì)立刻就處于開(kāi)啟狀態(tài)涩蜘。如果使用此方法后緊接者就需要進(jìn)行掃描,建議維護(hù)一個(gè)阻塞線程熏纯,內(nèi)部每隔一段時(shí)間查詢藍(lán)牙是否處于開(kāi)啟狀態(tài)同诫,外部顯示等待UI引導(dǎo)用戶等待,直至開(kāi)啟成功樟澜。使用第二種方法误窖,會(huì)通過(guò)系統(tǒng)彈出框的形式引導(dǎo)用戶開(kāi)啟,最終通過(guò)onActivityResult的形式回調(diào)通知是否開(kāi)啟成功秩贰。

  • 6.0及以上機(jī)型動(dòng)態(tài)獲取位置權(quán)限霹俺。
    藍(lán)牙打開(kāi)之后,進(jìn)行掃描之前毒费,需要判斷下當(dāng)前設(shè)備是否是6.0及以上丙唧,如果是,需要?jiǎng)討B(tài)獲取之前在Manifest中聲明的位置權(quán)限觅玻。

  • 配置掃描規(guī)則
    掃描規(guī)則可以配置1個(gè)或多個(gè)想际,也可以不配置使用默認(rèn)(掃描10秒)。掃描的時(shí)候溪厘,會(huì)根據(jù)配置的過(guò)濾選項(xiàng)胡本,對(duì)掃描到的設(shè)備進(jìn)行過(guò)濾,結(jié)果返回過(guò)濾后的設(shè)備畸悬。掃描時(shí)間配置為小于等于0侧甫,會(huì)實(shí)現(xiàn)無(wú)限掃描,直至調(diào)用BleManger.getInstance().cancelScan()來(lái)中止掃描蹋宦。

      BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
              .setServiceUuids(serviceUuids)      // 只掃描指定的服務(wù)的設(shè)備披粟,可選
              .setDeviceName(true, names)         // 只掃描指定廣播名的設(shè)備,可選
              .setDeviceMac(mac)                  // 只掃描指定mac的設(shè)備冷冗,可選
              .setAutoConnect(isAutoConnect)      // 連接時(shí)的autoConnect參數(shù)守屉,可選,默認(rèn)false
              .setScanTimeOut(10000)              // 掃描超時(shí)時(shí)間贾惦,可選胸梆,默認(rèn)10秒
              .build();
      BleManager.getInstance().initScanRule(scanRuleConfig);
    

    以上準(zhǔn)備工作完成后,就可以開(kāi)始進(jìn)行掃描须板。

      BleManager.getInstance().scan(new BleScanCallback() {
          @Override
          public void onScanStarted(boolean success) {
          }
    
          @Override
          public void onLeScan(BleDevice bleDevice) {
          }
    
          @Override
          public void onScanning(BleDevice bleDevice) {
          }
    
          @Override
          public void onScanFinished(List<BleDevice> scanResultList) {
          }
      });
    

onScanStarted(boolean success): 會(huì)回到主線程碰镜,參數(shù)表示本次掃描動(dòng)作是否開(kāi)啟成功。由于藍(lán)牙沒(méi)有打開(kāi)习瑰,上一次掃描沒(méi)有結(jié)束等原因绪颖,會(huì)造成掃描開(kāi)啟失敗。
onLeScan(BleDevice bleDevice):掃描過(guò)程中所有被掃描到的結(jié)果回調(diào)。由于掃描及過(guò)濾的過(guò)程是在工作線程中的柠横,此方法也處于工作線程中窃款。同一個(gè)設(shè)備會(huì)在不同的時(shí)間,攜帶自身不同的狀態(tài)(比如信號(hào)強(qiáng)度等)牍氛,出現(xiàn)在這個(gè)回調(diào)方法中晨继,出現(xiàn)次數(shù)取決于周圍的設(shè)備量及外圍設(shè)備的廣播間隔。
onScanning(BleDevice bleDevice):掃描過(guò)程中的所有過(guò)濾后的結(jié)果回調(diào)搬俊。與onLeScan區(qū)別之處在于:它會(huì)回到主線程紊扬;同一個(gè)設(shè)備只會(huì)出現(xiàn)一次;出現(xiàn)的設(shè)備是經(jīng)過(guò)掃描過(guò)濾規(guī)則過(guò)濾后的設(shè)備唉擂。
onScanFinished(List<BleDevice> scanResultList):本次掃描時(shí)段內(nèi)所有被掃描且過(guò)濾后的設(shè)備集合餐屎。它會(huì)回到主線程,相當(dāng)于onScanning設(shè)備之和玩祟。

1.4. 設(shè)備信息

掃描得到的BLE外圍設(shè)備腹缩,會(huì)以BleDevice對(duì)象的形式,作為后續(xù)操作的最小單元對(duì)象空扎。它本身含有這些信息:
String getName():藍(lán)牙廣播名
String getMac():藍(lán)牙Mac地址
byte[] getScanRecord(): 被掃描到時(shí)候攜帶的廣播數(shù)據(jù)
int getRssi() :被掃描到時(shí)候的信號(hào)強(qiáng)度
后續(xù)進(jìn)行設(shè)備連接藏鹊、斷開(kāi)、判斷設(shè)備狀態(tài)勺卢,讀寫操作等時(shí)候伙判,都會(huì)用到這個(gè)對(duì)象象对『诔溃可以把它理解為外圍藍(lán)牙設(shè)備的載體,所有對(duì)外圍藍(lán)牙設(shè)備的操作勒魔,都通過(guò)這個(gè)對(duì)象來(lái)傳導(dǎo)甫煞。

1.5. 連接、斷連冠绢、監(jiān)控連接狀態(tài)

拿到設(shè)備對(duì)象之后抚吠,可以進(jìn)行連接操作。

    BleManager.getInstance().connect(bleDevice, new BleGattCallback() {
        @Override
        public void onStartConnect() {
        }

        @Override
        public void onConnectFail(BleException exception) {
        }

        @Override
        public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {
        }

        @Override
        public void onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status) {
        }
    });

onStartConnect():開(kāi)始進(jìn)行連接弟胀。
onConnectFail(BleException exception):連接不成功楷力。
onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status):連接成功并發(fā)現(xiàn)服務(wù)。
onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status):連接斷開(kāi)孵户,特指連接后再斷開(kāi)的情況萧朝。在這里可以監(jiān)控設(shè)備的連接狀態(tài),一旦連接斷開(kāi)夏哭,可以根據(jù)自身情況考慮對(duì)BleDevice對(duì)象進(jìn)行重連操作检柬。需要注意的是,斷開(kāi)和重連之間最好間隔一段時(shí)間竖配,否則可能會(huì)出現(xiàn)長(zhǎng)時(shí)間連接不上的情況何址。此外里逆,如果通過(guò)調(diào)用disconnect(BleDevice bleDevice)方法,主動(dòng)斷開(kāi)藍(lán)牙連接的結(jié)果也會(huì)在這個(gè)方法中回調(diào)用爪,此時(shí)isActiveDisConnected將會(huì)是true原押。

1.6. GATT協(xié)議

BLE連接都是建立在 GATT (Generic Attribute Profile) 協(xié)議之上。GATT 是一個(gè)在藍(lán)牙連接之上的發(fā)送和接收很短的數(shù)據(jù)段的通用規(guī)范偎血,這些很短的數(shù)據(jù)段被稱為屬性(Attribute)班眯。它定義兩個(gè) BLE 設(shè)備通過(guò)Service 和 Characteristic 進(jìn)行通信。GATT 就是使用了 ATT(Attribute Protocol)協(xié)議烁巫,ATT 協(xié)議把 Service, Characteristic以及對(duì)應(yīng)的數(shù)據(jù)保存在一個(gè)查找表中署隘,次查找表使用 16 bit ID 作為每一項(xiàng)的索引。

關(guān)于GATT這部分內(nèi)容會(huì)在下面重點(diǎn)講解亚隙〈挪停總之,中心設(shè)備和外設(shè)需要雙向通信的話阿弃,唯一的方式就是建立 GATT 連接诊霹。當(dāng)連接成功之后,外圍設(shè)備與中心設(shè)備之間就建立起了GATT連接渣淳。
上面講到的connect(BleDevice bleDevice, BleGattCallback bleGattCallback)方法其實(shí)是有返回值的脾还,這個(gè)返回值就是BluetoothGatt。當(dāng)然還有其他方式可以獲取BluetoothGatt對(duì)象入愧,連接成功后鄙漏,調(diào)用:

BluetoothGatt gatt = BleManager.getInstance().getBluetoothGatt(BleDevice bleDevice);

通過(guò)BluetoothGatt對(duì)象作為連接橋梁棺蛛,中心設(shè)備可以獲取外圍設(shè)備的很多信息怔蚌,以及雙向通信。

首先旁赊,就可以獲取這個(gè)藍(lán)牙設(shè)備所擁有的Service和Characteristic桦踊。每一個(gè)屬性都可以被定義作不同的用途,通過(guò)它們來(lái)進(jìn)行協(xié)議通信终畅。下面的方法籍胯,就是通過(guò)BluetoothGatt,查找出所有的Service和Characteristic的UUID:

    List<BluetoothGattService> serviceList = bluetoothGatt.getServices();
    for (BluetoothGattService service : serviceList) {
        UUID uuid_service = service.getUuid();

        List<BluetoothGattCharacteristic> characteristicList= service.getCharacteristics();
        for(BluetoothGattCharacteristic characteristic : characteristicList) {
            UUID uuid_chara = characteristic.getUuid();
        }
    }

1.7. 協(xié)議通信

APP與設(shè)備建立了連接离福,并且知道了Service和Characteristic(需要與硬件協(xié)議溝通確認(rèn))之后杖狼,我們就可以通過(guò)BLE協(xié)議進(jìn)行通信了。通信的橋梁术徊,主要就是是通過(guò) 標(biāo)準(zhǔn)的或者自定義的Characteristic本刽,中文我們稱之為“特征”。我們可以從 Characteristic 讀數(shù)據(jù)和寫數(shù)據(jù)。這樣就實(shí)現(xiàn)了雙向的通信子寓。站在APP作為中心設(shè)備的角度暗挑,常用于數(shù)據(jù)交互的通信方式主要有3種:接收通知、寫斜友、讀炸裆,此外還有設(shè)置最大傳輸單元,獲取實(shí)時(shí)信號(hào)強(qiáng)度等通信操作鲜屏。

  • 接收通知
    有兩種方式可以接收通知烹看,indicate和notify。indicate和notify的區(qū)別就在于洛史,indicate是一定會(huì)收到數(shù)據(jù)惯殊,notify有可能會(huì)丟失數(shù)據(jù)。indicate底層封裝了應(yīng)答機(jī)制也殖,如果沒(méi)有收到中央設(shè)備的回應(yīng)土思,會(huì)再次發(fā)送直至成功;而notify不會(huì)有central收到數(shù)據(jù)的回應(yīng)忆嗜,可能無(wú)法保證數(shù)據(jù)到達(dá)的準(zhǔn)確性己儒,優(yōu)勢(shì)是速度快。通常情況下捆毫,當(dāng)外圍設(shè)備需要不斷地發(fā)送數(shù)據(jù)給APP的時(shí)候闪湾,比如血壓計(jì)在測(cè)量過(guò)程中的壓力變化,胎心儀在監(jiān)護(hù)過(guò)程中的實(shí)時(shí)數(shù)據(jù)傳輸绩卤,這種頻繁的情況下途样,優(yōu)先考慮notify形式。當(dāng)只需要發(fā)送很少且很重要的一條數(shù)據(jù)給APP的時(shí)候省艳,優(yōu)先考慮indicate形式娘纷。當(dāng)然,從Android開(kāi)發(fā)角度的出發(fā)跋炕,如果硬件放已經(jīng)考慮了成熟的協(xié)議和發(fā)送方式,我們需要做的僅僅是根據(jù)其配置的數(shù)據(jù)發(fā)送方式進(jìn)行相應(yīng)的對(duì)接即可律适。
    打開(kāi)notify

      BleManager.getInstance().notify(
              bleDevice,
              uuid_service,
              uuid_characteristic_notify,
              new BleNotifyCallback() {
                  @Override
                  public void onNotifySuccess() {
                      // 打開(kāi)通知操作成功
                  }
    
                  @Override
                  public void onNotifyFailure(BleException exception) {
                      // 打開(kāi)通知操作失敗
                  }
    
                  @Override
                  public void onCharacteristicChanged(byte[] data) {
                      // 打開(kāi)通知后辐烂,設(shè)備發(fā)過(guò)來(lái)的數(shù)據(jù)將在這里出現(xiàn)
                  }
              });
    

    關(guān)閉notify

      BleManager.getInstance().stopNotify(uuid_service, uuid_characteristic_notify);
    

    打開(kāi)indicate

      BleManager.getInstance().indicate(
              bleDevice,
              uuid_service,
              uuid_characteristic_indicate,
              new BleIndicateCallback() {
                  @Override
                  public void onIndicateSuccess() {
                      // 打開(kāi)通知操作成功
                  }
    
                  @Override
                  public void onIndicateFailure(BleException exception) {
                      // 打開(kāi)通知操作失敗
                  }
    
                  @Override
                  public void onCharacteristicChanged(byte[] data) {
                      // 打開(kāi)通知后,設(shè)備發(fā)過(guò)來(lái)的數(shù)據(jù)將在這里出現(xiàn)
                  }
              });
    

    關(guān)閉indicate

      BleManager.getInstance().stopIndicate(uuid_service, uuid_characteristic_indicate);
    

    這里的通知操作用到了兩個(gè)關(guān)鍵的參數(shù)捂贿,uuid_serviceuuid_characteristic_notify(或uuid_characteristic_indicate)纠修,就是上面提到的Service和Characteristic,此處以字符串的形式體現(xiàn)厂僧,不區(qū)分大小寫扣草。

  • 讀寫

      BleManager.getInstance().read(
              bleDevice,
              uuid_service,
              uuid_characteristic_read,
              new BleReadCallback() {
                  @Override
                  public void onReadSuccess(byte[] data) {
                      // 讀特征值數(shù)據(jù)成功
                  }
    
                  @Override
                  public void onReadFailure(BleException exception) {
                      // 讀特征值數(shù)據(jù)失敗
                  }
              });
    
      BleManager.getInstance().write(
              bleDevice,
              uuid_service,
              uuid_characteristic_write,
              data,
              new BleWriteCallback() {
                  @Override
                  public void onWriteSuccess(int current, int total, byte[] justWrite) {
                      // 發(fā)送數(shù)據(jù)到設(shè)備成功(分包發(fā)送的情況下,可以通過(guò)方法中返回的參數(shù)可以查看發(fā)送進(jìn)度)
                  }
    
                  @Override
                  public void onWriteFailure(BleException exception) {
                      // 發(fā)送數(shù)據(jù)到設(shè)備失敗
                  }
              });
    

    進(jìn)行BLE數(shù)據(jù)相互發(fā)送的時(shí)候,一次最多能發(fā)送20個(gè)字節(jié)辰妙。如果需要發(fā)送的數(shù)據(jù)超過(guò)20個(gè)字節(jié)鹰祸,有兩種方法,一種是主動(dòng)嘗試拓寬MTU密浑,另一種是采用分包傳輸?shù)姆绞酵苡ぁ?蚣苤械膚rite方法尔破,當(dāng)遇到數(shù)據(jù)超過(guò)20字節(jié)的情況時(shí)街图,默認(rèn)是進(jìn)行分包發(fā)送的。

  • 設(shè)置最大傳輸單元MTU

      BleManager.getInstance().setMtu(bleDevice, mtu, new BleMtuChangedCallback() {
          @Override
          public void onSetMTUFailure(BleException exception) {
              // 設(shè)置MTU失敗
          }
    
          @Override
          public void onMtuChanged(int mtu) {
              // 設(shè)置MTU成功懒构,并獲得當(dāng)前設(shè)備傳輸支持的MTU值
          }
      });
    
  • 獲取設(shè)備的實(shí)時(shí)信號(hào)強(qiáng)度Rssi

      BleManager.getInstance().readRssi(
              bleDevice,
              new BleRssiCallback() {
    
                  @Override
                  public void onRssiFailure(BleException exception) {
                      // 讀取設(shè)備的信號(hào)強(qiáng)度失敗
                  }
    
                  @Override
                  public void onRssiSuccess(int rssi) {
                      // 讀取設(shè)備的信號(hào)強(qiáng)度成功
                  }
              });
    

在BLE設(shè)備通信過(guò)程中餐济,有幾點(diǎn)經(jīng)驗(yàn)分享給大家:

  • 兩次操作之間最好間隔一小段時(shí)間,如100ms(具體時(shí)間可以根據(jù)自己實(shí)際藍(lán)牙外設(shè)自行嘗試延長(zhǎng)或縮短)胆剧。舉例颤介,onConnectSuccess之后,延遲100ms再進(jìn)行notify赞赖,之后再延遲100ms進(jìn)行write滚朵。
  • 連接及連接后的過(guò)程中,時(shí)刻關(guān)注onDisConnected方法前域,然后做處理辕近。
  • 斷開(kāi)后如果需要重連,也請(qǐng)延遲一段時(shí)間匿垄,否則會(huì)造成阻塞移宅。

2. BLE開(kāi)發(fā)實(shí)踐方面的理解

在分解FastBle源碼之前,我首先介紹一下BLE通信一些理論知識(shí)椿疗。

2.1 藍(lán)牙簡(jiǎn)介

藍(lán)牙是一種近距離無(wú)線通信技術(shù)漏峰。它的特性就是近距離通信,典型距離是 10 米以內(nèi)届榄,傳輸速度最高可達(dá) 24 Mbps浅乔,支持多連接,安全性高铝条,非常適合用智能設(shè)備上靖苇。

2.2 藍(lán)牙技術(shù)的版本演進(jìn)

  • 1999年發(fā)布1.0版本,目前市面上已很少見(jiàn)到班缰;
  • 2002年發(fā)布1.1版本贤壁,目前市面上已很少見(jiàn)到;
  • 2004年發(fā)布2.0版本埠忘,目前市面上已很少見(jiàn)到脾拆;
  • 2007年發(fā)布的2.1版本馒索,是之前使用最廣的,也是我們所謂的經(jīng)典藍(lán)牙名船。
  • 2009年推出藍(lán)牙 3.0版本绰上,也就是所謂的高速藍(lán)牙,傳輸速率理論上可高達(dá)24 Mbit/s包帚;
  • 2010年推出藍(lán)牙4.0版本渔期,它是相對(duì)之前版本的集大成者,它包括經(jīng)典藍(lán)牙渴邦、高速藍(lán)牙和藍(lán)牙低功耗協(xié)議疯趟。經(jīng)典藍(lán)牙包括舊有藍(lán)牙協(xié)議,高速藍(lán)牙基于Wi-Fi谋梭,低功耗藍(lán)牙就是BLE信峻。
  • 2016年藍(lán)牙技術(shù)聯(lián)盟提出了新的藍(lán)牙技術(shù)標(biāo)準(zhǔn),即藍(lán)牙5.0版本瓮床。藍(lán)牙5.0針對(duì)低功耗設(shè)備速度有相應(yīng)提升和優(yōu)化盹舞,結(jié)合wifi對(duì)室內(nèi)位置進(jìn)行輔助定位,提高傳輸速度隘庄,增加有效工作距離踢步,主要是針對(duì)物聯(lián)網(wǎng)方向的改進(jìn)。

2.3 Android上BLE功能的逐步演進(jìn)

在Android開(kāi)發(fā)過(guò)程中丑掺,版本的碎片化一直是需要考慮的問(wèn)題获印,再加上廠商定制及藍(lán)牙本身也和Android一樣一直在發(fā)展過(guò)程中,所以對(duì)于每一個(gè)版本支持什么功能街州,是我們需要知道的兼丰。

  • Android 4.3 開(kāi)始,開(kāi)始支持BLE功能唆缴,但只支持Central Mode(中心模式)
  • Android 5.0開(kāi)始鳍征,開(kāi)始支持Peripheral Mode(外設(shè)模式)

中心模式和外設(shè)模式是什么意思?

  • Central Mode: Android端作為中心設(shè)備面徽,連接其他外圍設(shè)備艳丛。
  • Peripheral Mode:Android端作為外圍設(shè)備,被其他中心設(shè)備連接斗忌。在Android 5.0支持外設(shè)模式之后质礼,才算實(shí)現(xiàn)了兩臺(tái)Android手機(jī)通過(guò)BLE進(jìn)行相互通信。

2.4 藍(lán)牙的廣播和掃描

以下內(nèi)容部分參考自BLE Introduction 织阳。
關(guān)于這部分內(nèi)容,需要引入一個(gè)概念砰粹,GAP(Generic Access Profile)唧躲,它用來(lái)控制設(shè)備連接和廣播造挽。GAP 使你的設(shè)備被其他設(shè)備可見(jiàn),并決定了你的設(shè)備是否可以或者怎樣與設(shè)備進(jìn)行交互弄痹。例如 Beacon 設(shè)備就只是向外發(fā)送廣播饭入,不支持連接;小米手環(huán)就可以與中心設(shè)備建立連接肛真。

在 GAP 中藍(lán)牙設(shè)備可以向外廣播數(shù)據(jù)包谐丢,廣播包分為兩部分: Advertising Data Payload(廣播數(shù)據(jù))和 Scan Response Data Payload(掃描回復(fù)),每種數(shù)據(jù)最長(zhǎng)可以包含 31 byte蚓让。這里廣播數(shù)據(jù)是必需的乾忱,因?yàn)橥庠O(shè)必需不停的向外廣播,讓中心設(shè)備知道它的存在历极。掃描回復(fù)是可選的窄瘟,中心設(shè)備可以向外設(shè)請(qǐng)求掃描回復(fù),這里包含一些設(shè)備額外的信息趟卸,例如設(shè)備的名字蹄葱。在 Android 中,系統(tǒng)會(huì)把這兩個(gè)數(shù)據(jù)拼接在一起锄列,返回一個(gè) 62 字節(jié)的數(shù)組图云。這些廣播數(shù)據(jù)可以自己手動(dòng)去解析,在 Android 5.0 也提供 ScanRecord 幫你解析邻邮,直接可以通過(guò)這個(gè)類獲得有意義的數(shù)據(jù)竣况。廣播中可以有哪些數(shù)據(jù)類型呢?設(shè)備連接屬性饶囚,標(biāo)識(shí)設(shè)備支持的 BLE 模式政恍,這個(gè)是必須的。設(shè)備名字绘梦,設(shè)備包含的關(guān)鍵 GATT service材蹬,或者 Service data,廠商自定義數(shù)據(jù)等等规惰。


廣播流程

外圍設(shè)備會(huì)設(shè)定一個(gè)廣播間隔睬塌,每個(gè)廣播間隔中,它會(huì)重新發(fā)送自己的廣播數(shù)據(jù)歇万。廣播間隔越長(zhǎng)揩晴,越省電,同時(shí)也不太容易掃描到贪磺。

剛剛講到硫兰,GAP決定了你的設(shè)備怎樣與其他設(shè)備進(jìn)行交互。答案是有2種方式:

  • 完全基于廣播的方式
    也有些情況是不需要連接的寒锚,只要外設(shè)廣播自己的數(shù)據(jù)即可劫映。用這種方式主要目的是讓外圍設(shè)備违孝,把自己的信息發(fā)送給多個(gè)中心設(shè)備。使用廣播這種方式最典型的應(yīng)用就是蘋果的 iBeacon泳赋。這是蘋果公司定義的基于 BLE 廣播實(shí)現(xiàn)的功能雌桑,可以實(shí)現(xiàn)廣告推送和室內(nèi)定位。這也說(shuō)明了祖今,APP 使用 BLE校坑,需要定位權(quán)限。

    基于非連接的千诬,這種應(yīng)用就是依賴 BLE 的廣播耍目,也叫作 Beacon。這里有兩個(gè)角色大渤,發(fā)送廣播的一方叫做 Broadcaster制妄,監(jiān)聽(tīng)廣播的一方叫 Observer。

  • 基于GATT連接的方式
    大部分情況下泵三,外設(shè)通過(guò)廣播自己來(lái)讓中心設(shè)備發(fā)現(xiàn)自己耕捞,并建立 GATT 連接,從而進(jìn)行更多的數(shù)據(jù)交換烫幕。這里有且僅有兩個(gè)角色俺抽,發(fā)起連接的一方,叫做中心設(shè)備—Central较曼,被連接的設(shè)備磷斧,叫做外設(shè)—Peripheral。

    • 外圍設(shè)備:這一般就是非常小或者簡(jiǎn)單的低功耗設(shè)備捷犹,用來(lái)提供數(shù)據(jù)弛饭,并連接到一個(gè)更加相對(duì)強(qiáng)大的中心設(shè)備,例如小米手環(huán)萍歉。
    • 中心設(shè)備:中心設(shè)備相對(duì)比較強(qiáng)大侣颂,用來(lái)連接其他外圍設(shè)備,例如手機(jī)等枪孩。

    GATT 連接需要特別注意的是:GATT 連接是獨(dú)占的憔晒。也就是一個(gè) BLE 外設(shè)同時(shí)只能被一個(gè)中心設(shè)備連接。一旦外設(shè)被連接蔑舞,它就會(huì)馬上停止廣播拒担,這樣它就對(duì)其他設(shè)備不可見(jiàn)了。當(dāng)設(shè)備斷開(kāi)攻询,它又開(kāi)始廣播从撼。中心設(shè)備和外設(shè)需要雙向通信的話,唯一的方式就是建立 GATT 連接钧栖。

    GATT 通信的雙方是 C/S 關(guān)系谋逻。外設(shè)作為 GATT 服務(wù)端(Server)呆馁,它維持了 ATT 的查找表以及 service 和 characteristic 的定義桐经。中心設(shè)備是 GATT 客戶端(Client)毁兆,它向 Server 發(fā)起請(qǐng)求。需要注意的是阴挣,所有的通信事件气堕,都是由客戶端發(fā)起,并且接收服務(wù)端的響應(yīng)畔咧。

2.5 BLE通信基礎(chǔ)

BLE通信的基礎(chǔ)有兩個(gè)重要的概念茎芭,ATT和GATT。

  • ATT
    全稱 attribute protocol誓沸,中文名“屬性協(xié)議”梅桩。它是 BLE 通信的基礎(chǔ)。ATT 把數(shù)據(jù)封裝拜隧,向外暴露為“屬性”宿百,提供“屬性”的為服務(wù)端,獲取“屬性”的為客戶端洪添。ATT 是專門為低功耗藍(lán)牙設(shè)計(jì)的垦页,結(jié)構(gòu)非常簡(jiǎn)單,數(shù)據(jù)長(zhǎng)度很短干奢。

  • GATT
    全稱 Generic Attribute Profile痊焊, 中文名“通用屬性配置文件”。它是在ATT 的基礎(chǔ)上忿峻,對(duì) ATT 進(jìn)行的進(jìn)一步邏輯封裝薄啥,定義數(shù)據(jù)的交互方式和含義。GATT是我們做 BLE 開(kāi)發(fā)的時(shí)候直接接觸的概念逛尚。

  • GATT 層級(jí)
    GATT按照層級(jí)定義了4個(gè)概念:配置文件(Profile)垄惧、服務(wù)(Service)、特征(Characteristic)和描述(Descriptor)黑低。他們的關(guān)系是這樣的:Profile 就是定義了一個(gè)實(shí)際的應(yīng)用場(chǎng)景赘艳,一個(gè) Profile包含若干個(gè) Service,一個(gè) Service 包含若干個(gè) Characteristic克握,一個(gè) Characteristic 可以包含若干 Descriptor蕾管。


    GATT層級(jí)
  • Profile
    Profile 并不是實(shí)際存在于 BLE 外設(shè)上的,它只是一個(gè)被 Bluetooth SIG 或者外設(shè)設(shè)計(jì)者預(yù)先定義的 Service 的集合菩暗。例如心率Profile(Heart Rate Profile)就是結(jié)合了 Heart Rate Service 和 Device Information Service掰曾。所有官方通過(guò) GATT Profile 的列表可以從這里找到。

  • Service
    Service 是把數(shù)據(jù)分成一個(gè)個(gè)的獨(dú)立邏輯項(xiàng)停团,它包含一個(gè)或者多個(gè) Characteristic旷坦。每個(gè) Service 有一個(gè) UUID 唯一標(biāo)識(shí)掏熬。 UUID 有 16 bit 的,或者 128 bit 的秒梅。16 bit 的 UUID 是官方通過(guò)認(rèn)證的旗芬,需要花錢購(gòu)買,128 bit 是自定義的捆蜀,這個(gè)就可以自己隨便設(shè)置疮丛。官方通過(guò)了一些標(biāo)準(zhǔn) Service,完整列表在這里辆它。以 Heart Rate Service為例誊薄,可以看到它的官方通過(guò) 16 bit UUID 是 0x180D,包含 3 個(gè) Characteristic:Heart Rate Measurement, Body Sensor LocationHeart Rate Control Point锰茉,并且定義了只有第一個(gè)是必須的呢蔫,它是可選實(shí)現(xiàn)的。

  • Characteristic
    需要重點(diǎn)提一下Characteristic飒筑, 它定義了數(shù)值和操作片吊,包含一個(gè)Characteristic聲明、Characteristic屬性扬霜、值定鸟、值的描述(Optional)。通常我們講的 BLE 通信著瓶,其實(shí)就是對(duì) Characteristic 的讀寫或者訂閱通知联予。比如在實(shí)際操作過(guò)程中,我對(duì)某一個(gè)Characteristic進(jìn)行讀材原,就是獲取這個(gè)Characteristic的value沸久。

  • UUID
    Service、Characteristic 和 Descriptor 都是使用 UUID 唯一標(biāo)示的余蟹。

    UUID 是全局唯一標(biāo)識(shí)卷胯,它是 128bit 的值,為了便于識(shí)別和閱讀威酒,一般以 “8位-4位-4位-4位-12位”的16進(jìn)制標(biāo)示窑睁,比如“12345678-abcd-1000-8000-123456000000”。

    但是葵孤,128bit的UUID 太長(zhǎng)担钮,考慮到在低功耗藍(lán)牙中,數(shù)據(jù)長(zhǎng)度非常受限的情況尤仍,藍(lán)牙又使用了所謂的 16 bit 或者 32 bit 的 UUID箫津,形式如下:“0000XXXX-0000-1000-8000-00805F9B34FB”。除了 “XXXX” 那幾位以外,其他都是固定苏遥,所以說(shuō)饼拍,其實(shí) 16 bit UUID 是對(duì)應(yīng)了一個(gè) 128 bit 的 UUID。這樣一來(lái)田炭,UUID 就大幅減少了师抄,例如 16 bit UUID只有有限的 65536(16的四次方) 個(gè)。與此同時(shí)诫肠,因?yàn)閿?shù)量有限司澎,所以 16 bit UUID 并不能隨便使用。藍(lán)牙技術(shù)聯(lián)盟已經(jīng)預(yù)先定義了一些 UUID栋豫,我們可以直接使用,比如“00001011-0000-1000-8000-00805F9B34FB”就一個(gè)是常見(jiàn)于BLE設(shè)備中的UUID谚殊。當(dāng)然也可以花錢定制自定義的UUID丧鸯。


3. FastBle源碼解析

通過(guò)上面BLE的基礎(chǔ)理論,我們可以分析到嫩絮,BLE通信實(shí)際上就是先由客戶端發(fā)起與服務(wù)端的連接丛肢,再通過(guò)服務(wù)端的找到其Characteristic進(jìn)行兩者間的數(shù)據(jù)交互。

在FastBle源碼中剿干,首先看BleManager中的connect()方法:

public BluetoothGatt connect(BleDevice bleDevice, BleGattCallback bleGattCallback) {
    if (bleGattCallback == null) {
        throw new IllegalArgumentException("BleGattCallback can not be Null!");
    }

    if (!isBlueEnable()) {
        BleLog.e("Bluetooth not enable!");
        bleGattCallback.onConnectFail(new OtherException("Bluetooth not enable!"));
        return null;
    }

    if (Looper.myLooper() == null || Looper.myLooper() != Looper.getMainLooper()) {
        BleLog.w("Be careful: currentThread is not MainThread!");
    }

    if (bleDevice == null || bleDevice.getDevice() == null) {
        bleGattCallback.onConnectFail(new OtherException("Not Found Device Exception Occurred!"));
    } else {
        BleBluetooth bleBluetooth = new BleBluetooth(bleDevice);
        boolean autoConnect = bleScanRuleConfig.isAutoConnect();
        return bleBluetooth.connect(bleDevice, autoConnect, bleGattCallback);
    }

    return null;
}

這個(gè)方法將掃描到的外圍設(shè)備對(duì)象傳入蜂怎,通過(guò)一些必要的條件判斷之后,調(diào)用bleBluetooth.connect()進(jìn)行連接置尔。我們?nèi)タ匆幌?code>BleBluetooth這個(gè)類:

public BleBluetooth(BleDevice bleDevice) {
    this.bleDevice = bleDevice;
}

上面的BleBluetooth的構(gòu)造方法是傳入一個(gè)藍(lán)牙設(shè)備對(duì)象杠步。由此可見(jiàn),一個(gè)BleBluetooth可能代表你的Android與這一個(gè)外圍設(shè)備整個(gè)交互過(guò)程榜轿,從開(kāi)始連接幽歼,到中間數(shù)據(jù)交互,一直到斷開(kāi)連接的整個(gè)過(guò)程谬盐。在多連接情況下甸私,有多少外圍設(shè)備,設(shè)備池中就維護(hù)著多少個(gè)BleBluetooth對(duì)象飞傀。

MultipleBluetoothController就是控制多設(shè)備連接的皇型。它里面有增加和移除設(shè)備的方法,如下圖的addBleBluetoothremoveBleBluetooth砸烦,傳入的參數(shù)就是BleBluetooth對(duì)象弃鸦,驗(yàn)證了上面的說(shuō)法。

public synchronized void addBleBluetooth(BleBluetooth bleBluetooth) {
    if (bleBluetooth == null) {
        return;
    }
    if (!bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {
        bleLruHashMap.put(bleBluetooth.getDeviceKey(), bleBluetooth);
    }
}

public synchronized void removeBleBluetooth(BleBluetooth bleBluetooth) {
    if (bleBluetooth == null) {
        return;
    }
    if (bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {
        bleLruHashMap.remove(bleBluetooth.getDeviceKey());
    }
}

回到BleBlutoothconnect方法:

public synchronized BluetoothGatt connect(BleDevice bleDevice,
                                          boolean autoConnect,
                                          BleGattCallback callback) {
    addConnectGattCallback(callback);
    isMainThread = Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper();
    BluetoothGatt gatt;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
                autoConnect, coreGattCallback, TRANSPORT_LE);
    } else {
        gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
                autoConnect, coreGattCallback);
    }
    if (gatt != null) {
        if (bleGattCallback != null)
            bleGattCallback.onStartConnect();
        connectState = BleConnectState.CONNECT_CONNECTING;
    }
    return gatt;
}

可見(jiàn)外冀,最終也是調(diào)用了原生API中的BluetoothDeviceconnectGatt()方法寡键。在藍(lán)牙原理分析中講到,連接過(guò)程中要?jiǎng)?chuàng)建一個(gè)BluetoothGattCallback,用來(lái)作為回調(diào)西轩,這個(gè)類非常重要员舵,所有的 GATT 操作的回調(diào)都在這里。而此處的coreGattCallback應(yīng)該就扮演著這個(gè)角色藕畔,它是BluetoothGattCallback的實(shí)現(xiàn)類對(duì)象马僻,對(duì)操作回調(diào)結(jié)果做了封裝和分發(fā)。

private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);

        if (newState == BluetoothGatt.STATE_CONNECTED) {
            gatt.discoverServices();

        } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
            closeBluetoothGatt();
            BleManager.getInstance().getMultipleBluetoothController().removeBleBluetooth(BleBluetooth.this);

            if (connectState == BleConnectState.CONNECT_CONNECTING) {
                connectState = BleConnectState.CONNECT_FAILURE;

                if (isMainThread) {
                    Message message = handler.obtainMessage();
                    message.what = BleMsg.MSG_CONNECT_FAIL;
                    message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);
                    handler.sendMessage(message);
                } else {
                    if (bleGattCallback != null)
                        bleGattCallback.onConnectFail(new ConnectException(gatt, status));
                }

            } else if (connectState == BleConnectState.CONNECT_CONNECTED) {
                connectState = BleConnectState.CONNECT_DISCONNECT;

                if (isMainThread) {
                    Message message = handler.obtainMessage();
                    message.what = BleMsg.MSG_DISCONNECTED;
                    BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);
                    para.setAcitive(isActiveDisconnect);
                    para.setBleDevice(getDevice());
                    message.obj = para;
                    handler.sendMessage(message);
                } else {
                    if (bleGattCallback != null)
                        bleGattCallback.onDisConnected(isActiveDisconnect, bleDevice, gatt, status);
                }
            }
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        BleLog.i("BluetoothGattCallback:onServicesDiscovered "
                + '\n' + "status: " + status
                + '\n' + "currentThread: " + Thread.currentThread().getId());

        if (status == BluetoothGatt.GATT_SUCCESS) {
            bluetoothGatt = gatt;
            connectState = BleConnectState.CONNECT_CONNECTED;
            isActiveDisconnect = false;
            BleManager.getInstance().getMultipleBluetoothController().addBleBluetooth(BleBluetooth.this);

            if (isMainThread) {
                Message message = handler.obtainMessage();
                message.what = BleMsg.MSG_CONNECT_SUCCESS;
                BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);
                para.setBleDevice(getDevice());
                message.obj = para;
                handler.sendMessage(message);
            } else {
                if (bleGattCallback != null)
                    bleGattCallback.onConnectSuccess(getDevice(), gatt, status);
            }
        } else {
            closeBluetoothGatt();
            connectState = BleConnectState.CONNECT_FAILURE;

            if (isMainThread) {
                Message message = handler.obtainMessage();
                message.what = BleMsg.MSG_CONNECT_FAIL;
                message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);
                handler.sendMessage(message);
            } else {
                if (bleGattCallback != null)
                    bleGattCallback.onConnectFail(new ConnectException(gatt, status));
            }
        }
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);

        Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleNotifyCallback) {
                BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;
                if (characteristic.getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {
                    Handler handler = bleNotifyCallback.getHandler();
                    if (handler != null) {
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE;
                        message.obj = bleNotifyCallback;
                        Bundle bundle = new Bundle();
                        bundle.putByteArray(BleMsg.KEY_NOTIFY_BUNDLE_VALUE, characteristic.getValue());
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }

        iterator = bleIndicateCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleIndicateCallback) {
                BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;
                if (characteristic.getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {
                    Handler handler = bleIndicateCallback.getHandler();
                    if (handler != null) {
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_INDICATE_DATA_CHANGE;
                        message.obj = bleIndicateCallback;
                        Bundle bundle = new Bundle();
                        bundle.putByteArray(BleMsg.KEY_INDICATE_BUNDLE_VALUE, characteristic.getValue());
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        super.onDescriptorWrite(gatt, descriptor, status);

        Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleNotifyCallback) {
                BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;
                if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {
                    Handler handler = bleNotifyCallback.getHandler();
                    if (handler != null) {
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_NOTIFY_RESULT;
                        message.obj = bleNotifyCallback;
                        Bundle bundle = new Bundle();
                        bundle.putInt(BleMsg.KEY_NOTIFY_BUNDLE_STATUS, status);
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }

        iterator = bleIndicateCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleIndicateCallback) {
                BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;
                if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {
                    Handler handler = bleIndicateCallback.getHandler();
                    if (handler != null) {
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_INDICATE_RESULT;
                        message.obj = bleIndicateCallback;
                        Bundle bundle = new Bundle();
                        bundle.putInt(BleMsg.KEY_INDICATE_BUNDLE_STATUS, status);
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);

        Iterator iterator = bleWriteCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleWriteCallback) {
                BleWriteCallback bleWriteCallback = (BleWriteCallback) callback;
                if (characteristic.getUuid().toString().equalsIgnoreCase(bleWriteCallback.getKey())) {
                    Handler handler = bleWriteCallback.getHandler();
                    if (handler != null) {
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_WRITE_RESULT;
                        message.obj = bleWriteCallback;
                        Bundle bundle = new Bundle();
                        bundle.putInt(BleMsg.KEY_WRITE_BUNDLE_STATUS, status);
                        bundle.putByteArray(BleMsg.KEY_WRITE_BUNDLE_VALUE, characteristic.getValue());
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicRead(gatt, characteristic, status);

        Iterator iterator = bleReadCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleReadCallback) {
                BleReadCallback bleReadCallback = (BleReadCallback) callback;
                if (characteristic.getUuid().toString().equalsIgnoreCase(bleReadCallback.getKey())) {
                    Handler handler = bleReadCallback.getHandler();
                    if (handler != null) {
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_READ_RESULT;
                        message.obj = bleReadCallback;
                        Bundle bundle = new Bundle();
                        bundle.putInt(BleMsg.KEY_READ_BUNDLE_STATUS, status);
                        bundle.putByteArray(BleMsg.KEY_READ_BUNDLE_VALUE, characteristic.getValue());
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }
    }
};

在收到連接狀態(tài)注服、讀韭邓、寫、通知等操作的結(jié)果回調(diào)之后溶弟,通過(guò)消息隊(duì)列機(jī)制女淑,交由相應(yīng)的Handler去處理。那處理消息的Handler在哪里辜御?舉例其中的write操作Handler handler = bleWriteCallback.getHandler();鸭你,handler對(duì)象被包含在了這個(gè)write操作的callback中。

public abstract class BleBaseCallback {

    private String key;
    private Handler handler;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public Handler getHandler() {
        return handler;
    }

    public void setHandler(Handler handler) {
        this.handler = handler;
    }
}

所有的操作的callback都繼承自這個(gè)BleBaseCallback抽象類擒权,它有兩個(gè)成員變量袱巨。一個(gè)key,標(biāo)識(shí)著這個(gè)callback歸屬于哪一個(gè)Characteristic的操作碳抄;另一個(gè)handler愉老,用于傳遞底層發(fā)來(lái)的操作結(jié)果,最終將結(jié)果交由callback去拋給調(diào)用者剖效,完成一次接口回調(diào)嫉入。

private void handleCharacteristicWriteCallback(BleWriteCallback bleWriteCallback,
                                               String uuid_write) {
    if (bleWriteCallback != null) {
        writeMsgInit();
        bleWriteCallback.setKey(uuid_write);
        bleWriteCallback.setHandler(mHandler);
        mBleBluetooth.addWriteCallback(uuid_write, bleWriteCallback);
        mHandler.sendMessageDelayed(
                mHandler.obtainMessage(BleMsg.MSG_CHA_WRITE_START, bleWriteCallback),
                BleManager.getInstance().getOperateTimeout());
    }
}

上面這段源碼解釋了這個(gè)機(jī)制,每一次write操作之后贱鄙,都會(huì)對(duì)傳入的callback進(jìn)行唯一性標(biāo)記劝贸,再通過(guò)handler用來(lái)傳遞操作結(jié)果,同時(shí)將這個(gè)callback加入這個(gè)設(shè)備的BleBlutooth對(duì)象的callback池中管理逗宁。
這樣就形成了APP維持一個(gè)設(shè)備連接池映九,一個(gè)設(shè)備連接池管理多個(gè)設(shè)備管理者,一個(gè)設(shè)備管理者管理多個(gè)不同類別的callback集合瞎颗,一個(gè)callback集合中含有多個(gè)同類的不同特征的callback件甥。

架構(gòu)圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市哼拔,隨后出現(xiàn)的幾起案子引有,更是在濱河造成了極大的恐慌,老刑警劉巖倦逐,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件譬正,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)曾我,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門粉怕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人抒巢,你說(shuō)我怎么就攤上這事贫贝。” “怎么了蛉谜?”我有些...
    開(kāi)封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵稚晚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我型诚,道長(zhǎng)客燕,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任俺驶,我火速辦了婚禮幸逆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘暮现。我一直安慰自己,他們只是感情好楚昭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布栖袋。 她就那樣靜靜地躺著,像睡著了一般抚太。 火紅的嫁衣襯著肌膚如雪塘幅。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天尿贫,我揣著相機(jī)與錄音电媳,去河邊找鬼。 笑死庆亡,一個(gè)胖子當(dāng)著我的面吹牛匾乓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播又谋,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拼缝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了彰亥?” 一聲冷哼從身側(cè)響起咧七,我...
    開(kāi)封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎任斋,沒(méi)想到半個(gè)月后继阻,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年瘟檩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抹缕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芒帕,死狀恐怖歉嗓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情背蟆,我是刑警寧澤鉴分,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站带膀,受9級(jí)特大地震影響志珍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜垛叨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一伦糯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗽元,春花似錦敛纲、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至佩谷,卻和暖如春旁壮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谐檀。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工抡谐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桐猬。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓麦撵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親课幕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子厦坛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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