一、背景

本文主要给前端同学提供建议,对于android的原生开发者可能过于简单哦。

库:flutter_blue

flutter下使用该库开发ble应用,在调用如下方法时:

          await mCharacteristic.setNotifyValue(true);

出现:

E/flutter (18304): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: PlatformException(set_notification_error, could not locate CCCD descriptor for characteristic: xxxxx, null)
E/flutter (18304): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:569:7)
E/flutter (18304): #1      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:321:33)

二、分析

我们可以从log的报错信息:could not locate CCCD descriptor for characteristic 中可以看出报错的原因是没有找到cccd的描述符。

首先我们在github的Issues中找找,找到如下的修复信息:

 chaochaox1990 commented on 27 Dec 2019@chaochaox1990 same error, have you fixed it?yes I already fixed. All I do just change the value of CCCD.

change the value of CCCD!

那么CCCD是什么的,这里博主给大家介绍一下。在Android中的GATT中的可Notify的characteristic中,有个特征值的描述Descriptor(uuid:00002902-0000-1000-8000-00805f9b34fb),这个值由蓝牙硬件出厂的时候写入,主要作用就是characteristic的权限控制,例如,是否开启等。在Android中的构造方法如下:

  public BluetoothGattDescriptor(UUID uuid, int permissions) {initDescriptor(null, uuid, 0, permissions);}

一般来说uuid如上,是固定的,参数为其读写等权限。BluetoothGattDescriptor详细介绍

那么为什么插件会报这个错呢,我们进入其源码看一下,在bluetooth_characteristic.dart文件中,可以看到报错代码如下:

 await FlutterBlue.instance._channel.invokeMethod('setNotification', request.writeToBuffer());

这里还是看不出端倪来,那我们去github项目中的源码看一看,这里它调用的是setNotification这个方法。我们来到这个路径:android/src/main/java/com/pauldemarco/flutter_blue/FlutterBluePlugin.java,查看这部分代码:

 case "setNotification":{byte[] data = call.arguments();Protos.SetNotificationRequest request;try {request = Protos.SetNotificationRequest.newBuilder().mergeFrom(data).build();} catch (InvalidProtocolBufferException e) {result.error("RuntimeException", e.getMessage(), e);break;}BluetoothGatt gattServer;BluetoothGattCharacteristic characteristic;BluetoothGattDescriptor cccDescriptor;try {gattServer = locateGatt(request.getRemoteId());characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());cccDescriptor = characteristic.getDescriptor(CCCD_ID);if(cccDescriptor == null) {throw new Exception("could not locate CCCD descriptor for characteristic: " +characteristic.getUuid().toString());}} catch(Exception e) {result.error("set_notification_error", e.getMessage(), null);return;}byte[] value = null;if(request.getEnable()) {boolean canNotify = (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0;boolean canIndicate = (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0;if(!canIndicate && !canNotify) {result.error("set_notification_error", "the characteristic cannot notify or indicate", null);return;}if(canIndicate) {value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;}if(canNotify) {value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;}} else {value = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;}if(!gattServer.setCharacteristicNotification(characteristic, request.getEnable())){result.error("set_notification_error", "could not set characteristic notifications to :" + request.getEnable(), null);return;}if(!cccDescriptor.setValue(value)) {result.error("set_notification_error", "error when setting the descriptor value to: " + value, null);return;}if(!gattServer.writeDescriptor(cccDescriptor)) {result.error("set_notification_error", "error when writing the descriptor", null);return;}result.success(null);break;}

这部分就是在Android环境下执行的代码。从:

cccDescriptor = characteristic.getDescriptor(CCCD_ID);if(cccDescriptor == null) {throw new Exception("could not locate CCCD descriptor for characteristic: " +characteristic.getUuid().toString());}
    static final private UUID CCCD_ID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

可以看出,只要characteristic中没有uuid为"00002902-0000-1000-8000-00805f9b34fb"的descriptor,就会报错,下面notify的关键代码没有执行,因此notify失败。

gattServer.setCharacteristicNotification(characteristic, request.getEnable())

这看似苛刻,其实这就是标准。可以notify的characteristic必须要有uuid为:"00002902-0000-1000-8000-00805f9b34fb"的descriptor。很多硬件都会有这个问题呢?造成这样的原因就是国内蓝牙厂商开发标准参差不齐,要不是没有descriptor,就是descriptor的uuid不是标准的。那么为什么他们不统一标准的,因为在原生开发中,我们一般不这样苛刻,下面给大家看看我们原生一般怎么写的:

 String bluetoothGattInfoKey = entry.getKey();BluetoothGattChannel bluetoothGattInfoValue = entry.getValue();if (bluetoothGatt != null && bluetoothGattInfoValue.getCharacteristic() != null) {BreoLog.d("enable-" + bluetoothGattInfoValue.getCharacteristic().getUuid().toString());success = bluetoothGatt.setCharacteristicNotification(bluetoothGattInfoValue.getCharacteristic(), enable);}BluetoothGattDescriptor bluetoothGattDescriptor = null;if (bluetoothGattInfoValue.getCharacteristic() != null && bluetoothGattInfoValue.getDescriptor() != null) {bluetoothGattDescriptor = bluetoothGattInfoValue.getDescriptor();} else if (bluetoothGattInfoValue.getCharacteristic() != null && bluetoothGattInfoValue.getDescriptor() == null) {if (bluetoothGattInfoValue.getCharacteristic().getDescriptors() != null&& bluetoothGattInfoValue.getCharacteristic().getDescriptors().size() == 1) {bluetoothGattDescriptor = bluetoothGattInfoValue.getCharacteristic().getDescriptors().get(0);} else {bluetoothGattDescriptor = bluetoothGattInfoValue.getCharacteristic().getDescriptor(UUID.fromString(BleConstant.CLIENT_CHARACTERISTIC_CONFIG));}}if (bluetoothGattDescriptor != null) {bluetoothGattInfoValue.setDescriptor(bluetoothGattDescriptor);if (isIndication) {if (enable) {bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);} else {bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);}} else {if (enable) {bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);} else {bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);}}if (bluetoothGatt != null) {bluetoothGatt.writeDescriptor(bluetoothGattDescriptor);}}

看吧,这两步操作是并行的,有的话就设置一下,没有也很佛系的。

                                           

这里给大家看两张图,图一的就是不正常的,characteristic是支持notify的,但是没有descriptor。图二就是正常的,有descriptor。

三、解决方案

1、让ble模块的供应商把descriptor写进去(推荐)。

2、修改一下flutter_blue插件,不让它那么苛刻严格。

3、试一试其他插件。pub仓库

Flutter for ble 之set_notification_error, could not locate CCCD descriptor for characteristic分析(原生角度)相关推荐

  1. 解决在终端使用“flutter doctor”命令后报:“Unable to locate Andro“错误 以及android sdk目录中找不到tools/bin目录解决

    "Unable to locate Andro"错误说明电脑没有配置Android sdk环境变量 直接先上解决办法: 1.在系统变量中添加该变量:(如下图)  在Path中新建: ...

  2. CSR867x — IOS设备搜索到的BLE外设名称与实际名称不相符的问题分析

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XX  作       者:文化人 XX  联系方式:(或进群:471144274联系我) ...

  3. Flutter瘦身大作战

    作者:闲鱼技术-三莅 背景 闲鱼技术团队于2018年上半年率先引入了Flutter技术实现客户端开发,到目前为止成功改造并上线了复杂的商品详情和发布业务.随着改造业务的增多,安装包体积急剧上增.安装包 ...

  4. flutter 应用场景_Flutter混合开发的路由栈管理

    为了把 Flutter 引入到原生工程,我们需要把 Flutter 工程改造为原生工程的一个组件依赖,并以组件化的方式管理不同平台的 Flutter 构建产物,即 Android 平台使用 aar.i ...

  5. Google跨平台UI框架 Flutter beta 重磅发布,这里有你需要了解的一切

    2018年2月27日,在西班牙巴塞罗那世界移动大会上,Google发布 Flutter 的第一个 beta 版本.Flutter 是 Google 用以帮助开发者在 iOS 和 Android 两个平 ...

  6. 关于Flutter初始化流程,我必须告诉你的是...

    作者:闲鱼技术-然道 1. 引言 最近在做性能优化的时候发现,在混合栈开发中,第一次启动Flutter页面的耗时总会是第二次启动Flutter页面耗时的两倍左右,这样给人感觉很不好.分析发现第一次启动 ...

  7. flutter 局部状态和全局状态区别_给 Android 开发者的 Flutter 指南

    这篇文档旨在帮助 Android 开发者利用既有的 Android 知识来通过 Flutter 开发移动应用.如果你了解 Android 框架的基本知识,你就可以使用这篇文档作为 Flutter 开发 ...

  8. Flutter 1.17 | 2020 首个稳定版发布!

    作者 / Chris Sells, Product Manager, Flutter developer experience 很高兴为大家带来 Flutter 1.17,这也是我们 2020 年的第 ...

  9. 定位Flutter内存问题很难么?

    简介:flutter内存泄漏定位 作者:闲鱼技术-三莅 内存水位升高导致的稳定性问题严重影响app用户体验,所以开发者们非常关注Flutter的内存表现.随着Flutter业务越来越多,闲鱼也面临着o ...

  10. 超详解析Flutter渲染引擎|业务想创新,不了解底层原理怎么行?

    简介:Flutter 作为一个跨平台的应用框架,诞生之后,就被高度关注.它通过自绘 UI ,解决了之前 RN 和 weex 方案难以解决的多端一致性问题.Dart AOT 和精减的渲染管线,相对与 J ...

最新文章

  1. RMB符号的几种显示方式。
  2. 使用SharedPreferences进行数据存储
  3. 《从技术走向管理——李元芳履职记》读书心得
  4. c语言程序设计多数求最大值,C语言编程问题存在三个整数,使x²+y²+z²=625,求x+y+z的最大值。这是大一C语言实验书的习题,请不要用过于复杂的算法语句...
  5. 如何安装 Linux 下的 Adobe Reader
  6. 实际开发的存储过程_实际生产中的 Android SDK开发总结| 完结
  7. DropBox:机器学习每年可以为我们节省170万的文档预览费用
  8. 微信小程序项目实例——投骰子
  9. WPS标题编号级别,根据上一级自动编号
  10. 怎么进入修复计算机界面,开机进入启动修复界面不能启动win7电脑的修复办法...
  11. 官方大大的的邮寄快递到啦
  12. Zoom天使投资人Jim Scheinman:Zoom的诞生始末
  13. 利用cookie进行模拟登录并且抓取失败
  14. 线性非齐次微分方程的求解套路
  15. 【回溯专题】—— 回溯算法入门篇
  16. while (0) 是什么东东?
  17. 怎么用计算机打吃鸡,想要用电脑玩绝地求生手游吗?这几个模拟器满足你
  18. R语言ggplot2可视化:使用patchwork包将两个ggplot2可视化结果图像垂直堆叠排列进行组合构图(vertically stack the plots)
  19. wordpress添加媒体_如何在WordPress中添加新帖子并利用所有功能
  20. Ubuntu 安装一款常用的图像编辑软件

热门文章

  1. 餐厅点菜c语言程序代码,餐馆点菜系统C语言源代码.pdf
  2. Cannot connenct to relay host smtp.163.com (php邮件发送失败)
  3. 重力传感事件应用之一 手机摇一摇(摇一次得一分)
  4. 【数据分析】基于RFM模型的线上零售中的客户细分(二):RFM模型实战
  5. 企业级的周报管理系统源码 采用了目前极为流行的扁平化响应式的设计风格
  6. NVIDIA Jetson TK1学习与开发——简介(针对嵌入式系统应用释放 GPU 的潜能)
  7. 启动Hadoop都哪些进程?它们的作用是什么
  8. 标梵微信小程序开发价格之做一个小程序要多少钱?
  9. HTML朗读可以用英文吗,关于英语朗读的方法技巧
  10. Requirement already satisfied的解决方案