Android蓝牙BLE集成多设备最佳实践

bbbv6178 7年前
   <h2>背景</h2>    <p>公司开发了一款健康类APP,用户可以通过APP连接外部蓝牙BLE设备采集血糖,血压,体重等多个常见健康类指标。因此APP需要同时集成多款设备(多个品牌的血糖仪,血压计,体脂秤等)。每个厂家的设备对接协议是不同的,甚至连同一个设备的不同版本,协议都会有差距。在一个APP中跟多个设备对接,甚至在同一个界面中需要处理多个设备,界面跟协议混在一起,成为一个比较头疼的问题。本文对如何在APP中支持多设备集成,并且在需要对接新设备的时候,容易扩展现有代码提供了一个比较好的实践思路。</p>    <h2>原有实现</h2>    <p>原来在界面Activity类中,集成多个设备时候的代码通常如下所示(为了达到说明目的,代码做了很多简化,实际情况中设备对接的协议代码逻辑要复杂的多):</p>    <pre>  <code class="language-java">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>    <h2>如何解决</h2>    <p>以上代码可以看出界面跟设备协议是强耦合在一起的。如果需要集成更多的设备那怎么办?原有的类代码势必变得更复杂,难以维护。因此我们需要把设备间的协议交互逻辑与界面进行解耦,以保持单一职责的设计原则:界面只进行步骤和测量结果的更新展示,交互逻辑可以放到其他类中。在这个地方我们可以使用Adapter作为设备适配器,把设备间的交互封装到Adapter里面去,集成不同设备的时候调用不同的Adapter处理即可。</p>    <p>我们如何设计Adapter呢?虽然每个设备的交互协议不一样,但是其中一些操作却是共性的,比如一开始总是要连接设备,连接成功后设置指定UUID的notification或者indication,然后向外部设备写入数据(下发指令),或者等待外部设备数据变化上报,交互完成后再断开设备。</p>    <p>因此我们可以把这些共性操作抽象成DeviceAdapter接口。DeviceAdatper接口主要包含上述的常用操作:</p>    <pre>  <code class="language-java">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) //解析外部设备发过来的数据</code></pre>    <p>经过进一步的调研我们发现,设备的连接,断开连接,设置notification/indication,写入数据,读取数据,这些操作本身都是完全一样的,不同的是我们对协议数据本身的解析。所以这些操作我们可以用一个默认的抽象类DefaultAdapter来实现,DefaultAdapter实现DeviceAdapter接口,把对数据解析的功能延迟到子类去进行。针对A设备创建DeviceAdapterA继承于DefaultAdapter,B设备创建DeviceAdapterB继承于DefaultAdapter,不同的设备用不同的Adapter去处理。</p>    <p>如图所示:</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/646c0b502857d53edbd720e39c44bc3d.png"></p>    <h2>解耦关键</h2>    <p>Adapter设计完成后,那调用模块(Client)又是如何知道针对A设备,用DeviceAdapterA处理;针对B设备用DeviceAdapterB处理的呢?</p>    <p>我们需要做到两点:</p>    <ol>     <li>Adapter需要告诉客户:它能处理哪些设备。</li>     <li>需要把adapter管理起来,连接设备后需要能找到相匹配的adapter去处理设备。</li>    </ol>    <p>解决第1点很简单,我们只需要在DeviceAdapter增加一个方法用来标识它能处理哪些设备,方法如下:</p>    <pre>  <code class="language-java">String[] supportedNames() //返回的String数组代表它能处理的设备名组合</code></pre>    <p>第2点解决起来要复杂些,我们需要增加一个管理类BleCenterManager(门面模式),BleCenterManager的主要职责为:管理维护adapter,并对不同设备找到相匹配的adapter进行处理,主要包含如下方法:</p>    <pre>  <code class="language-java">public void startScan() //开始蓝牙扫描  public void stopScan()  //停止蓝牙扫描  public void connectThenStart(BleDevice device) //连接并处理设备  public void addDeviceAdapterFactory(DeviceAdapter.Factory factory) //增加Adapter相应的Factory</code></pre>    <p>Adapter创建</p>    <p>在深入讲解BleCenterManager之前,我们可以先谈谈adapter的创建。adapter主要由客户代码根据交互协议创建,初始化的过程可能各不相同。因此BleCenterManager最好不直接创建adapter,委托相应的Factory进行,也就是通常所说的工厂方法模式。客户提供adapter的时候,需提供与之对应的Factory,BleCenterManager负责管理这些factories,创建adapter的时候只需要调用factory.buildDeviceAdapter()方法即可。Factory针对抽象编程,设计为抽象类,核心代码如下:</p>    <pre>  <code class="language-java">abstract class Factory{    protected BleCenterManager mBleCenterManager;    public Factory(BleCenterManager bleCenterManager) {      mBleCenterManager = bleCenterManager;    }    public abstract DeviceAdapter buildDeviceAdapter();    @Override    public String toString() {      return "Factory{}"+getClass().getName();    }  }</code></pre>    <p>Factory与adapter之间关系如下:</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/7a5cd8ad61afb8c44e6e3ae89724196e.png"></p>    <p>查找Adapter进行处理</p>    <p>Adapter和Factory设计完后,通过bleCenterManager.addDeviceAdapterFactory()方法添加到BleCenterManager内部的factory列表,添加factory的同时,factory创建对应的adapter并加入到adapter列表。添加完之后,BleCenterManager是如何找到device相匹配的adapter进行处理的呢?答案很简单,逐一遍历adapter列表,查找adapter的supportedNames()方法返回的String列表是否包含设备名。查找到第一个就返回,如果列表遍历后查找不到就抛出异常。核心代码如下:</p>    <p>查找Adapter</p>    <pre>  <code class="language-java">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);  }</code></pre>    <p>查找到adapter后,调用adapter.connectThenStart()方法进行后续协议交互处理。</p>    <p>看完Adapter这部分,很多人都会觉得有些熟悉,这个设计跟Retrofit的CallAdapter很类似。对的,好的设计都是相通的,只是换了个形式,都是常用设计模式:适配器,工厂,单例等的组合。</p>    <h2>结束语</h2>    <p>通过Adapter与BleCenterManager的结合实现了协议逻辑与APP界面的解耦。上文中的代码只是基本核心示例代码,完整代码已经开源到Github: <a href="/misc/goto?guid=4959735593450476446" rel="nofollow,noindex">https://github.com/nziyouren/EasyBle</a> ,欢迎大家contribute。目前库还处于初级阶段,后续逐步会加一些功能,比如从网络加载adapter,如何在APP不升级版本的情况下,动态扩展集成能力。</p>    <p>  </p>   <p> </p>    <p></p>    <p>来自:http://www.infoq.com/cn/articles/android-bluetooth-ble</p>    <p> </p>