1. 程式人生 > >FastBle藍芽低功耗框架的使用

FastBle藍芽低功耗框架的使用

因為自己的專案中有用到了藍芽相關的功能,所以之前也斷斷續續地針對藍芽通訊尤其是BLE通訊進行了一番探索,整理出了一個開源框架FastBle與各位分享經驗。 原始碼地址:

隨著對FastBle框架關注的人越來越多,與我討論問題的小夥伴也多起來,所以整理了一篇文章,詳細介紹一下框架的用法,一些坑,還有我對Android BLE開發實踐方面的理解。

文章分為3個部分:

  • FastBle的使用
  • BLE開發實踐方面的理解
  • FastBle原始碼解析

1. FastBle的使用

1.1 宣告許可權

<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 : 這個許可權允許程式連線到已配對的藍芽裝置, 請求連線/接收連線/傳輸資料需要改許可權, 主要用於對配對後進行操作;
  • android.permission.BLUETOOTH_ADMIN : 這個許可權允許程式發現和配對藍芽裝置, 該許可權用來管理藍芽裝置, 有了這個許可權, 應用才能使用本機的藍芽裝置, 主要用於對配對前的操作;
  • android.permission.ACCESS_COARSE_LOCATION和android.permission.ACCESS_FINE_LOCATION:Android 6.0以後,這兩個許可權是必須的,藍芽掃描周圍的裝置需要獲取模糊的位置資訊。這兩個許可權屬於同一組危險許可權,在清單檔案中宣告之後,還需要再執行時動態獲取。

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);
}

在使用之前,需要事先呼叫初始化init(Application app)方法。此外,可以進行一些自定義的配置,比如是否顯示框架內部日誌,重連次數和重連時間間隔,以及操作超時時間。

1.3. 掃描外圍裝置

APP作為中心裝置,想要與外圍硬體裝置建立藍芽通訊的前提是首先得到裝置物件,途徑是掃描。在呼叫掃描方法之前,你首先應該先處理下面的準備工作。

  • 判斷當前Android裝置是否支援BLE。 Android 4.3以後系統中加入了藍芽BLE的功能。

     BleManager.getInstance().isSupportBle();
    
  • 判斷當前Android裝置的藍芽是否已經開啟。 可以直接呼叫下面的判斷方法來判斷本機是否已經打開了藍芽,如果沒有,向用戶丟擲提示。

    BleManager.getInstance().isBlueEnable();
    
  • 主動開啟藍芽。 除了判斷藍芽是否開啟給以使用者提示之外,我們也可以通過程式直接幫助使用者開啟藍芽開關,開啟方式有這幾種: 方法1:通過藍芽介面卡直接開啟藍芽。

    BleManager.getInstance().enableBluetooth();
    

    方法2:通過startActivityForResult引導介面引導使用者開啟藍芽。

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

    需要注意的是,第一種方法是非同步的,開啟藍芽需要一段時間,呼叫此方法後,藍芽不會立刻就處於開啟狀態。如果使用此方法後緊接者就需要進行掃描,建議維護一個阻塞執行緒,內部每隔一段時間查詢藍芽是否處於開啟狀態,外部顯示等待UI引導使用者等待,直至開啟成功。使用第二種方法,會通過系統彈出框的形式引導使用者開啟,最終通過onActivityResult的形式回撥通知是否開啟成功。

  • 6.0及以上機型動態獲取位置許可權。 藍芽開啟之後,進行掃描之前,需要判斷下當前裝置是否是6.0及以上,如果是,需要動態獲取之前在Manifest中宣告的位置許可權。

  • 配置掃描規則 掃描規則可以配置1個或多個,也可以不配置使用預設(掃描10秒)。掃描的時候,會根據配置的過濾選項,對掃描到的裝置進行過濾,結果返回過濾後的裝置。掃描時間配置為小於等於0,會實現無限掃描,直至呼叫BleManger.getInstance().cancelScan()來中止掃描。

      BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
              .setServiceUuids(serviceUuids)      // 只掃描指定的服務的裝置,可選
              .setDeviceName(true, names)         // 只掃描指定廣播名的裝置,可選
              .setDeviceMac(mac)                  // 只掃描指定mac的裝置,可選
              .setAutoConnect(isAutoConnect)      // 連線時的autoConnect引數,可選,預設false
              .setScanTimeOut(10000)              // 掃描超時時間,可選,預設10秒
              .build();
      BleManager.getInstance().initScanRule(scanRuleConfig);
    

    以上準備工作完成後,就可以開始進行掃描。

      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): 會回到主執行緒,引數表示本次掃描動作是否開啟成功。由於藍芽沒有開啟,上一次掃描沒有結束等原因,會造成掃描開啟失敗。onLeScan(BleDevice bleDevice):掃描過程中所有被掃描到的結果回撥。由於掃描及過濾的過程是在工作執行緒中的,此方法也處於工作執行緒中。同一個裝置會在不同的時間,攜帶自身不同的狀態(比如訊號強度等),出現在這個回撥方法中,出現次數取決於周圍的裝置量及外圍裝置的廣播間隔。onScanning(BleDevice bleDevice):掃描過程中的所有過濾後的結果回撥。與onLeScan區別之處在於:它會回到主執行緒;同一個裝置只會出現一次;出現的裝置是經過掃描過濾規則過濾後的裝置。onScanFinished(List<BleDevice> scanResultList):本次掃描時段內所有被掃描且過濾後的裝置集合。它會回到主執行緒,相當於onScanning裝置之和。

1.4. 裝置資訊

掃描得到的BLE外圍裝置,會以BleDevice物件的形式,作為後續操作的最小單元物件。它本身含有這些資訊:String getName():藍芽廣播名String getMac():藍芽Mac地址byte[] getScanRecord(): 被掃描到時候攜帶的廣播資料int getRssi() :被掃描到時候的訊號強度 後續進行裝置連線、斷開、判斷裝置狀態,讀寫操作等時候,都會用到這個物件。可以把它理解為外圍藍芽裝置的載體,所有對外圍藍芽裝置的操作,都通過這個物件來傳導。

1.5. 連線、斷連、監控連線狀態

拿到裝置物件之後,可以進行連線操作。

    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():開始進行連線。onConnectFail(BleException exception):連線不成功。onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status):連線成功並發現服務。onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status):連線斷開,特指連線後再斷開的情況。在這裡可以監控裝置的連線狀態,一旦連線斷開,可以根據自身情況考慮對BleDevice物件進行重連操作。需要注意的是,斷開和重連之間最好間隔一段時間,否則可能會出現長時間連線不上的情況。此外,如果通過呼叫disconnect(BleDevice bleDevice)方法,主動斷開藍芽連線的結果也會在這個方法中回撥,此時isActiveDisConnected將會是true。

1.6. GATT協議

BLE連線都是建立在 GATT (Generic Attribute Profile) 協議之上。GATT 是一個在藍芽連線之上的傳送和接收很短的資料段的通用規範,這些很短的資料段被稱為屬性(Attribute)。它定義兩個 BLE 裝置通過Service 和 Characteristic 進行通訊。GATT 就是使用了 ATT(Attribute Protocol)協議,ATT 協議把 Service, Characteristic以及對應的資料儲存在一個查詢表中,次查詢表使用 16 bit ID 作為每一項的索引。

關於GATT這部分內容會在下面重點講解。總之,中心裝置和外設需要雙向通訊的話,唯一的方式就是建立 GATT 連線。當連線成功之後,外圍裝置與中心裝置之間就建立起了GATT連線。 上面講到的connect(BleDevice bleDevice, BleGattCallback bleGattCallback)方法其實是有返回值的,這個返回值就是BluetoothGatt。當然還有其他方式可以獲取BluetoothGatt物件,連線成功後,呼叫:

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

通過BluetoothGatt物件作為連線橋樑,中心裝置可以獲取外圍裝置的很多資訊,以及雙向通訊。

首先,就可以獲取這個藍芽裝置所擁有的Service和Characteristic。每一個屬性都可以被定義作不同的用途,通過它們來進行協議通訊。下面的方法,就是通過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. 協議通訊

APP與裝置建立了連線,並且知道了Service和Characteristic(需要與硬體協議溝通確認)之後,我們就可以通過BLE協議進行通訊了。通訊的橋樑,主要就是是通過 標準的或者自定義的Characteristic,中文我們稱之為“特徵”。我們可以從 Characteristic 讀資料和寫資料。這樣就實現了雙向的通訊。站在APP作為中心裝置的角度,常用於資料互動的通訊方式主要有3種:接收通知、寫、讀,此外還有設定最大傳輸單元,獲取實時訊號強度等通訊操作。

  • 接收通知 有兩種方式可以接收通知,indicate和notify。indicate和notify的區別就在於,indicate是一定會收到資料,notify有可能會丟失資料。indicate底層封裝了應答機制,如果沒有收到中央裝置的迴應,會再次傳送直至成功;而notify不會有central收到資料的迴應,可能無法保證資料到達的準確性,優勢是速度快。通常情況下,當外圍裝置需要不斷地傳送資料給APP的時候,比如血壓計在測量過程中的壓力變化,胎心儀在監護過程中的實時資料傳輸,這種頻繁的情況下,優先考慮notify形式。當只需要傳送很少且很重要的一條資料給APP的時候,優先考慮indicate形式。當然,從Android開發角度的出發,如果硬體放已經考慮了成熟的協議和傳送方式,我們需要做的僅僅是根據其配置的資料傳送方式進行相應的對接即可。開啟notify

      BleManager.getInstance().notify(
              bleDevice,
              uuid_service,
              uuid_characteristic_notify,
              new BleNotifyCallback() {
                  @Override
                  public void onNotifySuccess() {
                      // 開啟通知操作成功
                  }
    
                  @Override
                  public void onNotifyFailure(BleException exception) {
                      // 開啟通知操作失敗
                  }
    
                  @Override
                  public void onCharacteristicChanged(byte[] data) {
                      // 開啟通知後,裝置發過來的資料將在這裡出現
                  }
              });
    

    關閉notify

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

    開啟indicate

      BleManager.getInstance().indicate(
              bleDevice,
              uuid_service,
              uuid_characteristic_indicate,
              new BleIndicateCallback() {
                  @Override
                  public void onIndicateSuccess() {
                      // 開啟通知操作成功
                  }
    
                  @Override
                  public void onIndicateFailure(BleException exception) {
                      // 開啟通知操作失敗
                  }
    
                  @Override
                  public void onCharacteristicChanged(byte[] data) {
                      // 開啟通知後,裝置發過來的資料將在這裡出現
                  }
              });
    

    關閉indicate

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

    這裡的通知操作用到了兩個關鍵的引數,uuid_serviceuuid_characteristic_notify(或uuid_characteristic_indicate),就是上面提到的Service和Characteristic,此處以字串的形式體現,不區分大小寫。

  • 讀寫

      BleManager.getInstance().read(
              bleDevice,
              uuid_service,
              uuid_characteristic_read,
              new BleReadCallback() {
                  @Override
                  public void onReadSuccess(byte[] data) {
                      // 讀特徵值資料成功
                  }
    
                  @Override
                  public void onReadFailure(BleException exception) {
                      // 讀特徵值資料失敗
                  }
              });
    
      BleManager.getInstance().write(
              bleDevice,
              uuid_service,
              uuid_characteristic_write,
              data,
              new BleWriteCallback() {
                  @Override
                  public void onWriteSuccess(int current, int total, byte[] justWrite) {
                      // 傳送資料到裝置成功(分包傳送的情況下,可以通過方法中返回的引數可以檢視傳送進度)
                  }
    
                  @Override
                  public void onWriteFailure(BleException exception) {
                      // 傳送資料到裝置失敗
                  }
              });
    

    進行BLE資料相互發送的時候,一次最多能傳送20個位元組。如果需要傳送的資料超過20個位元組,有兩種方法,一種是主動嘗試拓寬MTU,另一種是採用分包傳輸的方式。框架中的write方法,當遇到資料超過20位元組的情況時,預設是進行分包傳送的。

  • 設定最大傳輸單元MTU

      BleManager.getInstance().setMtu(bleDevice, mtu, new BleMtuChangedCallback() {
          @Override
          public void onSetMTUFailure(BleException exception) {
              // 設定MTU失敗
          }
    
          @Override
          public void onMtuChanged(int mtu) {
              // 設定MTU成功,並獲得當前裝置傳輸支援的MTU值
          }
      });
    
  • 獲取裝置的實時訊號強度Rssi

      BleManager.getInstance().readRssi(
              bleDevice,
              new BleRssiCallback() {
    
                  @Override
                  public void onRssiFailure(BleException exception) {
                      // 讀取裝置的訊號強度失敗
                  }
    
                  @Override
                  public void onRssiSuccess(int rssi) {
                      // 讀取裝置的訊號強度成功
                  }
              });
    

在BLE裝置通訊過程中,有幾點經驗分享給大家:

  • 兩次操作之間最好間隔一小段時間,如100ms(具體時間可以根據自己實際藍芽外設自行嘗試延長或縮短)。舉例,onConnectSuccess之後,延遲100ms再進行notify,之後再延遲100ms進行write
  • 連線及連線後的過程中,時刻關注onDisConnected方法,然後做處理。
  • 斷開後如果需要重連,也請延遲一段時間,否則會造成阻塞。

2. BLE開發實踐方面的理解

在分解FastBle原始碼之前,我首先介紹一下BLE通訊一些理論知識。

2.1 藍芽簡介

藍芽是一種近距離無線通訊技術。它的特性就是近距離通訊,典型距離是 10 米以內,傳輸速度最高可達 24 Mbps,支援多連線,安全性高,非常適合用智慧裝置上。

2.2 藍芽技術的版本演進

  • 1999年釋出1.0版本,目前市面上已很少見到;
  • 2002年釋出1.1版本,目前市面上已很少見到;
  • 2004年釋出2.0版本,目前市面上已很少見到;
  • 2007年釋出的2.1版本,是之前使用最廣的,也是我們所謂的經典藍芽。
  • 2009年推出藍芽 3.0版本,也就是所謂的高速藍芽,傳輸速率理論上可高達24 Mbit/s;
  • 2010年推出藍芽4.0版本,它是相對之前版本的集大成者,它包括經典藍芽、高速藍芽和藍芽低功耗協議。經典藍芽包括舊有藍芽協議,高速藍芽基於Wi-Fi,低功耗藍芽就是BLE。
  • 2016年藍芽技術聯盟提出了新的藍芽技術標準,即藍芽5.0版本。藍芽5.0針對低功耗裝置速度有相應提升和優化,結合wifi對室內位置進行輔助定位,提高傳輸速度,增加有效工作距離,主要是針對物聯網方向的改進。

2.3 Android上BLE功能的逐步演進

在Android開發過程中,版本的碎片化一直是需要考慮的問題,再加上廠商定製及藍芽本身也和Android一樣一直在發展過程中,所以對於每一個版本支援什麼功能,是我們需要知道的。

  • Android 4.3 開始,開始支援BLE功能,但只支援Central Mode(中心模式)
  • Android 5.0開始,開始支援Peripheral Mode(外設模式)

中心模式和外設模式是什麼意思?

  • Central Mode: Android端作為中心裝置,連線其他外圍裝置。
  • Peripheral Mode:Android端作為外圍裝置,被其他中心裝置連線。在Android 5.0支援外設模式之後,才算實現了兩臺Android手機通過BLE進行相互通訊。

2.4 藍芽的廣播和掃描

以下內容部分參考自BLE Introduction 。 關於這部分內容,需要引入一個概念,GAP(Generic Access Profile),它用來控制裝置連線和廣播。GAP 使你的裝置被其他裝置可見,並決定了你的裝置是否可以或者怎樣與裝置進行互動。例如 Beacon 裝置就只是向外傳送廣播,不支援連線;小米手環就可以與中心裝置建立連線。

在 GAP 中藍芽裝置可以向外廣播資料包,廣播包分為兩部分: Advertising Data Payload(廣播資料)和 Scan Response Data Payload(掃描回覆),每種資料最長可以包含 31 byte。這裡廣播資料是必需的,因為外設必需不停的向外廣播,讓中心裝置知道它的存在。掃描回覆是可選的,中心裝置可以向外設請求掃描回覆,這裡包含一些裝置額外的資訊,例如裝置的名字。在 Android 中,系統會把這兩個資料拼接在一起,返回一個 62 位元組的陣列。這些廣播資料可以自己手動去解析,在 Android 5.0 也提供 ScanRecord 幫你解析,直接可以通過這個類獲得有意義的資料。廣播中可以有哪些資料型別呢?裝置連線屬性,標識裝置支援的 BLE 模式,這個是必須的。裝置名字,裝置包含的關鍵 GATT service,或者 Service data,廠商自定義資料等等。

廣播流程

外圍裝置會設定一個廣播間隔,每個廣播間隔中,它會重新發送自己的廣播資料。廣播間隔越長,越省電,同時也不太容易掃描到。

剛剛講到,GAP決定了你的裝置怎樣與其他裝置進行互動。答案是有2種方式:

  • 完全基於廣播的方式 也有些情況是不需要連線的,只要外設廣播自己的資料即可。用這種方式主要目的是讓外圍裝置,把自己的資訊傳送給多箇中心裝置。使用廣播這種方式最典型的應用就是蘋果的 iBeacon。這是蘋果公司定義的基於 BLE 廣播實現的功能,可以實現廣告推送和室內定位。這也說明了,APP 使用 BLE,需要定位許可權。

    基於非連線的,這種應用就是依賴 BLE 的廣播,也叫作 Beacon。這裡有兩個角色,傳送廣播的一方叫做 Broadcaster,監聽廣播的一方叫 Observer。

  • 基於GATT連線的方式 大部分情況下,外設通過廣播自己來讓中心裝置發現自己,並建立 GATT 連線,從而進行更多的資料交換。這裡有且僅有兩個角色,發起連線的一方,叫做中心裝置—Central,被連線的裝置,叫做外設—Peripheral。

    • 外圍裝置:這一般就是非常小或者簡單的低功耗裝置,用來提供資料,並連線到一個更加相對強大的中心裝置,例如小米手環。
    • 中心裝置:中心裝置相對比較強大,用來連線其他外圍裝置,例如手機等。

    GATT 連線需要特別注意的是:GATT 連線是獨佔的。也就是一個 BLE 外設同時只能被一箇中心裝置連線。一旦外設被連線,它就會馬上停止廣播,這樣它就對其他裝置不可見了。當裝置斷開,它又開始廣播。中心裝置和外設需要雙向通訊的話,唯一的方式就是建立 GATT 連線。

    GATT 通訊的雙方是 C/S 關係。外設作為 GATT 服務端(Server),它維持了 ATT 的查詢表以及 service 和 characteristic 的定義。中心裝置是 GATT 客戶端(Client),它向 Server 發起請求。需要注意的是,所有的通訊事件,都是由客戶端發起,並且接收服務端的響應。

2.5 BLE通訊基礎

BLE通訊的基礎有兩個重要的概念,ATT和GATT。

  • ATT 全稱 attribute protocol,中文名“屬性協議”。它是 BLE 通訊的基礎。ATT 把資料封裝,向外暴露為“屬性”,提供“屬性”的為服務端,獲取“屬性”的為客戶端。ATT 是專門為低功耗藍芽設計的,結構非常簡單,資料長度很短。

  • GATT 全稱 Generic Attribute Profile, 中文名“通用屬性配置檔案”。它是在ATT 的基礎上,對 ATT 進行的進一步邏輯封裝,定義資料的互動方式和含義。GATT是我們做 BLE 開發的時候直接接觸的概念。

  • GATT 層級 GATT按照層級定義了4個概念:配置檔案(Profile)、服務(Service)、特徵(Characteristic)和描述(Descriptor)。他們的關係是這樣的:Profile 就是定義了一個實際的應用場景,一個 Profile包含若干個 Service,一個 Service 包含若干個 Characteristic,一個 Characteristic 可以包含若干 Descriptor。

     

    GATT層級

  • Profile Profile 並不是實際存在於 BLE 外設上的,它只是一個被 Bluetooth SIG 或者外設設計者預先定義的 Service 的集合。例如心率Profile(Heart Rate Profile)就是結合了 Heart Rate Service 和 Device Information Service。所有官方通過 GATT Profile 的列表可以從這裡找到。

  • Service Service 是把資料分成一個個的獨立邏輯項,它包含一個或者多個 Characteristic。每個 Service 有一個 UUID 唯一標識。 UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通過認證的,需要花錢購買,128 bit 是自定義的,這個就可以自己隨便設定。官方通過了一些標準 Service,完整列表在這裡。以 Heart Rate Service為例,可以看到它的官方通過 16 bit UUID 是 0x180D,包含 3 個 Characteristic:Heart Rate Measurement, Body Sensor LocationHeart Rate Control Point,並且定義了只有第一個是必須的,它是可選實現的。

  • Characteristic 需要重點提一下Characteristic, 它定義了數值和操作,包含一個Characteristic宣告、Characteristic屬性、值、值的描述(Optional)。通常我們講的 BLE 通訊,其實就是對 Characteristic 的讀寫或者訂閱通知。比如在實際操作過程中,我對某一個Characteristic進行讀,就是獲取這個Characteristic的value。

  • UUID Service、Characteristic 和 Descriptor 都是使用 UUID 唯一標示的。

    UUID 是全域性唯一標識,它是 128bit 的值,為了便於識別和閱讀,一般以 “8位-4位-4位-4位-12位”的16進位制標示,比如“12345678-abcd-1000-8000-123456000000”。

    但是,128bit的UUID 太長,考慮到在低功耗藍芽中,資料長度非常受限的情況,藍芽又使用了所謂的 16 bit 或者 32 bit 的 UUID,形式如下:“0000XXXX-0000-1000-8000-00805F9B34FB”。除了 “XXXX” 那幾位以外,其他都是固定,所以說,其實 16 bit UUID 是對應了一個 128 bit 的 UUID。這樣一來,UUID 就大幅減少了,例如 16 bit UUID只有有限的 65536(16的四次方) 個。與此同時,因為數量有限,所以 16 bit UUID 並不能隨便使用。藍芽技術聯盟已經預先定義了一些 UUID,我們可以直接使用,比如“00001011-0000-1000-8000-00805F9B34FB”就一個是常見於BLE裝置中的UUID。當然也可以花錢定製自定義的UUID。

3. FastBle原始碼解析

通過上面BLE的基礎理論,我們可以分析到,BLE通訊實際上就是先由客戶端發起與服務端的連線,再通過服務端的找到其Characteristic進行兩者間的資料互動。

在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;
}

這個方法將掃描到的外圍裝置物件傳入,通過一些必要的條件判斷之後,呼叫bleBluetooth.connect()進行連線。我們去看一下BleBluetooth這個類:

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

上面的BleBluetooth的構造方法是傳入一個藍芽裝置物件。由此可見,一個BleBluetooth可能代表你的Android與這一個外圍裝置整個互動過程,從開始連線,到中間資料互動,一直到斷開連線的整個過程。在多連線情況下,有多少外圍裝置,裝置池中就維護著多少個BleBluetooth物件。

MultipleBluetoothController就是控制多裝置連線的。它裡面有增加和移除裝置的方法,如下圖的addBleBluetoothremoveBleBluetooth,傳入的引數就是BleBluetooth物件,驗證了上面的說法。

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;
}

可見,最終也是呼叫了原生API中的BluetoothDeviceconnectGatt()方法。在藍芽原理分析中講到,連線過程中要建立一個BluetoothGattCallback,用來作為回撥,這個類非常重要,所有的 GATT 操作的回撥都在這裡。而此處的coreGattCallback應該就扮演著這個角色,它是BluetoothGattCallback的實現類物件,對操作回撥結果做了封裝和分發。

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);
                    }
                }
            }
        }
    }
};

在收到連線狀態、讀、寫、通知等操作的結果回撥之後,通過訊息佇列機制,交由相應的Handler去處理。那處理訊息的Handler在哪裡?舉例其中的write操作Handler handler = bleWriteCallback.getHandler();,handler物件被包含在了這個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都繼承自這個BleBaseCallback抽象類,它有兩個成員變數。一個key,標識著這個callback歸屬於哪一個Characteristic的操作;另一個handler,用於傳遞底層發來的操作結果,最終將結果交由callback去拋給呼叫者,完成一次介面回撥。

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());
    }
}

上面這段原始碼解釋了這個機制,每一次write操作之後,都會對傳入的callback進行唯一性標記,再通過handler用來傳遞操作結果,同時將這個callback加入這個裝置的BleBlutooth物件的callback池中管理。 這樣就形成了APP維持一個裝置連線池,一個裝置連線池管理多個裝置管理者,一個裝置管理者管理多個不同類別的callback集合,一個callback集合中含有多個同類的不同特徵的callback。

架構圖

作者:陳利健 連結:https://www.jianshu.com/p/795bb0a08beb 來源:簡書 簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。