Android藍牙BLE集成多設備最佳實踐

bbbv6178 7年前發布 | 10K 次閱讀 安卓開發 Android開發 移動開發

背景

公司開發了一款健康類APP,用戶可以通過APP連接外部藍牙BLE設備采集血糖,血壓,體重等多個常見健康類指標。因此APP需要同時集成多款設備(多個品牌的血糖儀,血壓計,體脂秤等)。每個廠家的設備對接協議是不同的,甚至連同一個設備的不同版本,協議都會有差距。在一個APP中跟多個設備對接,甚至在同一個界面中需要處理多個設備,界面跟協議混在一起,成為一個比較頭疼的問題。本文對如何在APP中支持多設備集成,并且在需要對接新設備的時候,容易擴展現有代碼提供了一個比較好的實踐思路。

原有實現

原來在界面Activity類中,集成多個設備時候的代碼通常如下所示(為了達到說明目的,代碼做了很多簡化,實際情況中設備對接的協議代碼邏輯要復雜的多):

private DeviceType mConnectedDeviceType; //連接設備類型

/**

  • 處理集成設備發過來的數據
  • @param uuid
  • @param data */ private void processDeviceData(UUID uuid,byte[] data){ //首先判斷當前連接設備類型,再分別處理 if (mConnectedDeviceType == DeviceType.A){ //如果當前連接設備類型為A, 根據本地硬編碼的協議處理A類設備 processDeviceA(UUID uuid,byte[] data); }else if (mConnectedDeviceType == DeviceType.B){ //如果當前連接設備類型為B, 處理B類設備 processDeviceB(UUID uuid,byte[] data); }else if (mConnectedDeviceType == DeviceType.C){ //如果當前連接設備類型為C, 處理C類設備 processDeviceC(UUID uuid,byte[] data); } ... }

/**

  • 處理A類設備的協議交互代碼
  • @param uuid
  • @param data */ private void processDeviceA(UUID uuid,byte[] data){ int step = parseCmdA(uuid, data); if (step == 0x1){ showResultA(data); //顯示測量結果 }else if (step == 0x2){ writeCmd("cmd2a"); //向連接設備下發數據 }else if (step == 0x3){ writeCmd("cmd3a"); } }

/**

  • 處理B類設備的協議交互代碼
  • @param uuid
  • @param data */ private void processDeviceB(UUID uuid,byte[] data){ int step = parseCmdB(uuid, data); if (step == 0x1){ showResultB(data); //顯示測量結果 }else if (step == 0x2){ writeCmd("cmd2b"); //向連接設備下發數據 }else if (step == 0x3){ writeCmd("cmd3b"); } }

/**

  • 處理C類設備的協議交互代碼
  • @param uuid
  • @param data */ private void processDeviceC(UUID uuid,byte[] data){ int step = parseCmdC(uuid, data); if (step == 0x1){ writeCmd("cmd2c"); //向連接設備下發數據 }else if (step == 0x2){ showResultC(data); //顯示測量結果 }else if (step == 0x3){ writeCmd("cmd3c"); } }</code></pre>

    如何解決

    以上代碼可以看出界面跟設備協議是強耦合在一起的。如果需要集成更多的設備那怎么辦?原有的類代碼勢必變得更復雜,難以維護。因此我們需要把設備間的協議交互邏輯與界面進行解耦,以保持單一職責的設計原則:界面只進行步驟和測量結果的更新展示,交互邏輯可以放到其他類中。在這個地方我們可以使用Adapter作為設備適配器,把設備間的交互封裝到Adapter里面去,集成不同設備的時候調用不同的Adapter處理即可。

    我們如何設計Adapter呢?雖然每個設備的交互協議不一樣,但是其中一些操作卻是共性的,比如一開始總是要連接設備,連接成功后設置指定UUID的notification或者indication,然后向外部設備寫入數據(下發指令),或者等待外部設備數據變化上報,交互完成后再斷開設備。

    因此我們可以把這些共性操作抽象成DeviceAdapter接口。DeviceAdatper接口主要包含上述的常用操作:

    UUID[] notificationUUIDs()   //設置notification的UUID
    UUID[] indicatorUUIDs()       //設置indicator的UUID
    void connectThenStart(BleDevice bleDevice) //連接設備并進行協議交互
    void disconnect()           //斷開設備
    void writeCharacteristic(UUID uuid, byte[] data) //向指定UUID的Characteristic寫入數據
    void readCharacteristic(UUID uuid) //從指定UUID的Characteristic中讀取數據
    void executeCmd(int cmd) throws EasyBleException //執行命令接口
    void processData(UUID uuid, byte[] data) //解析外部設備發過來的數據

    經過進一步的調研我們發現,設備的連接,斷開連接,設置notification/indication,寫入數據,讀取數據,這些操作本身都是完全一樣的,不同的是我們對協議數據本身的解析。所以這些操作我們可以用一個默認的抽象類DefaultAdapter來實現,DefaultAdapter實現DeviceAdapter接口,把對數據解析的功能延遲到子類去進行。針對A設備創建DeviceAdapterA繼承于DefaultAdapter,B設備創建DeviceAdapterB繼承于DefaultAdapter,不同的設備用不同的Adapter去處理。

    如圖所示:

    (點擊放大圖像)

    解耦關鍵

    Adapter設計完成后,那調用模塊(Client)又是如何知道針對A設備,用DeviceAdapterA處理;針對B設備用DeviceAdapterB處理的呢?

    我們需要做到兩點:

    1. Adapter需要告訴客戶:它能處理哪些設備。
    2. 需要把adapter管理起來,連接設備后需要能找到相匹配的adapter去處理設備。

    解決第1點很簡單,我們只需要在DeviceAdapter增加一個方法用來標識它能處理哪些設備,方法如下:

    String[] supportedNames() //返回的String數組代表它能處理的設備名組合

    第2點解決起來要復雜些,我們需要增加一個管理類BleCenterManager(門面模式),BleCenterManager的主要職責為:管理維護adapter,并對不同設備找到相匹配的adapter進行處理,主要包含如下方法:

    public void startScan() //開始藍牙掃描
    public void stopScan()  //停止藍牙掃描
    public void connectThenStart(BleDevice device) //連接并處理設備
    public void addDeviceAdapterFactory(DeviceAdapter.Factory factory) //增加Adapter相應的Factory

    Adapter創建

    在深入講解BleCenterManager之前,我們可以先談談adapter的創建。adapter主要由客戶代碼根據交互協議創建,初始化的過程可能各不相同。因此BleCenterManager最好不直接創建adapter,委托相應的Factory進行,也就是通常所說的工廠方法模式。客戶提供adapter的時候,需提供與之對應的Factory,BleCenterManager負責管理這些factories,創建adapter的時候只需要調用factory.buildDeviceAdapter()方法即可。Factory針對抽象編程,設計為抽象類,核心代碼如下:

    abstract class Factory{
    protected BleCenterManager mBleCenterManager;
    public Factory(BleCenterManager bleCenterManager) {
      mBleCenterManager = bleCenterManager;
    }
    public abstract DeviceAdapter buildDeviceAdapter();
    @Override
    public String toString() {
      return "Factory{}"+getClass().getName();
    }
    }

    Factory與adapter之間關系如下:

    (點擊放大圖像)

    查找Adapter進行處理

    Adapter和Factory設計完后,通過bleCenterManager.addDeviceAdapterFactory()方法添加到BleCenterManager內部的factory列表,添加factory的同時,factory創建對應的adapter并加入到adapter列表。添加完之后,BleCenterManager是如何找到device相匹配的adapter進行處理的呢?答案很簡單,逐一遍歷adapter列表,查找adapter的supportedNames()方法返回的String列表是否包含設備名。查找到第一個就返回,如果列表遍歷后查找不到就拋出異常。核心代碼如下:

    查找Adapter

    private DeviceAdapter findAppropriateDeviceAdapter(BleDevice bleDevice) throws EasyBleException {
    //先判斷factory是否為空
    if (mDeviceAdapterFactories == null || mDeviceAdapterFactories.isEmpty()){
      throw new EasyBleException("Device adapter factories empty!");
    }
    //遍歷adapter列表
    for (DeviceAdapter adapter:mDeviceAdapters){
     String[] nameList = adapter.supportedNames();
       if (nameList != null && nameList.length > 0){
         for (String name:nameList){
           //查找到名字符合的就返回
           if (bleDevice.getDeviceName().equalsIgnoreCase(name)){
             return adapter;
           }
         }
       }
       String[] nameRegExpList = adapter.supportedNameRegExps();
       if (nameRegExpList != null && nameRegExpList.length >0){
         for (String nameRegExp:nameRegExpList){
           if (Pattern.matches(nameRegExp,bleDevice.getDeviceName())){
             return adapter;
           }
         }
       }
    }
    throw new EasyBleUnsupportedDeviceException(bleDevice);
    }

    查找到adapter后,調用adapter.connectThenStart()方法進行后續協議交互處理。

    看完Adapter這部分,很多人都會覺得有些熟悉,這個設計跟Retrofit的CallAdapter很類似。對的,好的設計都是相通的,只是換了個形式,都是常用設計模式:適配器,工廠,單例等的組合。

    結束語

    通過Adapter與BleCenterManager的結合實現了協議邏輯與APP界面的解耦。上文中的代碼只是基本核心示例代碼,完整代碼已經開源到Github: https://github.com/nziyouren/EasyBle ,歡迎大家contribute。目前庫還處于初級階段,后續逐步會加一些功能,比如從網絡加載adapter,如何在APP不升級版本的情況下,動態擴展集成能力。

     

     

    來自:http://www.infoq.com/cn/articles/android-bluetooth-ble

     

 本文由用戶 bbbv6178 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!