文章目录

  • 前言
  • 传统蓝牙 VS Ble蓝牙
  • cosplay 角色扮演
  • 一些基本概念
  • Ble开发的几个步骤
      • 准备
      • 第一步 配置清单文件
      • 第二步 检查设备,获取BluetoothAdapter
      • 第三步 注册广播,开启蓝牙
      • 第四步 扫描指定类型的设备
      • 第五步 连接设备,获取特征值
    • 容易碰到的一些错误和问题
    • 总结

前言

    以前一直忙于不断的做项目,总是觉得没有必要抽时间出来写博客,但看到业界的很多大牛都有坚持写博客的习惯,自己也应该向大佬们看齐才有机会成为大牛。其实坚持写博客的好处还是不少的,一方可以作为自己对所学知识的一个总结归纳,另外一个方面可以作为和其他人的一个技术分享交流,好处多多。但万事开头难,希望自己能坚持下来,keep moving!

    之前也做过几个Ble的项目,现在来对这一块做一个简单的介绍和总结。

传统蓝牙 VS Ble蓝牙

    在Android中蓝牙根据协议主要分为两种:一种是基于spp协议的传统蓝牙或者叫经典蓝牙,另外一种就是Android 4.3才引进的基于GATT协议的BLE(Bluetooth Low Energy)低功耗蓝牙,相比较于传统蓝牙其主要优点就是功耗低,搜索、连接速度相对较快,那是不是说相较于传统蓝牙它就没有缺点呢?答案是否定的,相较于传统蓝牙而言,Ble的数据传输效率低,每次可以传输的数据量小得多,单次最多只能传输20个byte,而传统蓝牙单次可传输的数据量远不止于此,但也正是由于功耗低这一主要优点,使得现在ble被广泛应用于智能穿戴设备、智能锁、心率测量仪等领域 (本文主要介绍的是ble蓝牙,若要了解spp协议蓝牙的相关开发,可以查看我我另一篇博客蓝牙开发(二)----- 基于SPP蓝牙协议的Android应用开发)。

cosplay 角色扮演

    在BLE协议中,有两个角色,周边(Periphery)和中央(Central)。周边是数据的提供者,中央是数据的使用和处理者。在Android SDK里面,Android4.3以后手机只能作为中央设备使用,直到Android5.0以后手机才可以作为周边设备使用,即此时的手机既可以作为BLE周边设备来为中央提供数据,也可以作为中央设备接收处理周边其它蓝牙设备传递过来的数据进行处理。 一个中央设备可以同时连接多个周边设备,但一个周边设备某一时刻只能连接一个中央设备。

一些基本概念

  • GATT协议
        我们已经知道了Ble是基于GATT协议的,那么什么是GATT协议呢?下面就来简单介绍一下:
        GATT是Generic Attributes的缩写,也就是通用属性的意思,它是ble低功耗蓝牙设备之间的标准通信协议。根据蓝牙技术官网的介绍,GATT协议定义了一个分层数据结构,该结构暴露给连接的Ble设备进行通信。其数据分层结构如下图所示:

GATT协议数据结构图
    简单描述一下结构图的意思就是:Profile是一组服务(Service)的集合,这个文件包含广播的种类、所使用的连接间隔、所需的安全等级等配置信息,每个Service下面可以包含多个特征(characteristic),每个特征包含属性(properties)和值(value),还可以包含多个描述(descriptor)。

  • Service
    服务是特征和与其它服​​务关系的集合。每个Service包含一个或多个characteristic,每个Service由一个唯一的UUID标识,UUID可以分为两种:一种是经过官方认证的16位UUID,另外一种是由开发者自己定义的128位的UUID,类似于0x0000xxxx-0000-1000-8000-00805F9B34FB,这是蓝牙技术联盟定义的一个基本格式,蓝牙模块的开发者通常只要定义xxxx的部分即可,开发时通常由硬件或嵌入式工程师会告诉我们该模块包含哪些Service及其对应的UUID。

  • Characteristic
    特征被定义为包含单个值的属性类型。和Service一样,Characteristic也是由一个UUID进行标识,通常也是由硬件或嵌入式工程师提供。Characteristic是我们进行数据通信的一个重要载体,我们的数据读、写、通知等都通过这个类来实现,是比较重要的一个类。

    这里就只介绍Service和Characteristic这两个比较重要的数据结构,Characteristic中的属性和描述等就不一一介绍了,有兴趣的可以去查阅相关资料。

Ble开发的几个步骤

准备

从硬件或嵌入式工程师手上拿到需要的UUID:

 public static final UUID UUID_SERVICE = UUID.fromString("0000fff2-0000-1000-8000-00805f9b34fb");  //主Service的UUIDpublic static final UUID UUID_NOTIFY = UUID.fromString("0000fff3-0000-1000-8000-00805f9b34fb"); //具有通知属性的UUIDpublic static final UUID UUID_READ   = UUID.fromString("0000fff5-0000-1000-8000-00805f9b34fb"); //具有读取属性的UUIDpublic static final UUID UUID_WRITE  = UUID.fromString("0000fff7-0000-1000-8000-00805f9b34fb"); //具有写入属性的UUID
第一步 配置清单文件
 <uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><!-- Android6.0及以上必须获取位置权限,否则无法扫描到周边的蓝牙设备  --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
第二步 检查设备,获取BluetoothAdapter

进行这步之前记得先进行关于定位权限的动态适配

 /*** 判断该设备是否支持Ble并获取BluetoothAdapter*/public Boolean ensureBLEExists() {if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {return false;}//获取BluetoothAdapterBluetoothManager bm = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);if (bm!=null) mBluetoothAdapter = bm.getAdapter();return true;}
第三步 注册广播,开启蓝牙
 /*** 注册蓝牙状态改变的监听广播*/private void registerBlueToothReceiver(){if (mBluetoothStateReceiver==null)mBluetoothStateReceiver=new BluetoothEnableStateReceiver();IntentFilter filter = new IntentFilter();filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);registerReceiver(mBluetoothStateReceiver,filter);}//蓝牙开关状态的广播接收者,可以通过设置接口回调进行监听,//以方便在蓝牙状态变化的时候做出相应操作或提示public class BluetoothEnableStateReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);Log.i(TAG, "BluetoothOnOffStateReceiver: state: " + state);if(state == BluetoothAdapter.STATE_ON) {//蓝牙打开} else if(state == BluetoothAdapter.STATE_TURNING_OFF){//蓝牙正在关闭} else if(state == BluetoothAdapter.STATE_OFF){//蓝牙已关闭}}}}/*** 开启蓝牙*/public void enableBluetooth() {if (mBluetoothAdapter!=null){if (!mBluetoothAdapter.isEnabled()) { //蓝牙未开启,通过隐式意图请求开启蓝牙Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableBtIntent, 0);}}}
第四步 扫描指定类型的设备

用户允许开启蓝牙之后接下来就可以扫描周边的设备了

public BluetoothAdapter.LeScanCallback mScanCallback = new BluetoothAdapter.LeScanCallback() {@Overridepublic void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { //device是设备对象,rssi是信号强度,scanRecord是扫描记录if (device != null) {//接口回调扫描到的设备synchronized (mCallBacks){for (BleAdapterCallBack callBack : mCallBacks) {callBack.onDeviceFound(device, rssi);}}}};/*** 开始扫描 10秒后自动停止* */private void startScan(){UUID[] uuid = {UUID_SERVICE };if(mIsScanning){ //如果当前正在扫描则先停止扫描mBluetoothAdapter.stopLeScan(mScanCallback);}//mBluetoothAdapter.startLeScan(scanCallback);//不进行特定设备过滤,扫描所有设备//进行特定uuid过滤,只扫描具有指定Service UUID的设备mBluetoothAdapter.startLeScan(uuid, mScanCallback);new Handler().postDelayed(new Runnable() {@Overridepublic void run() {//结束扫描mBluetoothAdapter.stopLeScan(scanCallback);}},10000);}

    蓝牙扫描是比较耗费资源的,如果扫描频率比较高或者时间比较长,在性能差一点手机上会出现电量消耗比较大和发热比较严重的情况,所以要设置适当的扫描时间。
    另外,我们可以调用mBluetoothAdapter.startLeScan(uuid, mScanCallback),扫描具有指定Service UUID的设备,也可以调用mBluetoothAdapter.startLeScan(scanCallback),扫描所有的蓝牙设备,可以根据不同的方法自行选择。

第五步 连接设备,获取特征值
BluetoothGattCallback  gattCallback = new BluetoothGattCallback() {//连接状态变化的回调@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {super.onConnectionStateChange(gatt, status, newState);Log.i(TAG, "连接状态:status:" + status + ",newState:" + newState)if (status == BluetoothGatt.GATT_SUCCESS) {if (newState == BluetoothProfile.STATE_CONNECTED) {//连接成功,调用发现服务的方法gatt.discoverServices();mHandler.sendEmptyMessage(STATE_CONNECTED);} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {Log.i(TAG, "断开连接");mHandler.sendEmptyMessage(STATE_DISCONNECT);gatt.disconnect();gatt.close();}} else {Log.i(TAG, "连接失败");gatt.close();}}//发现Service的回调@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);if (status == BluetoothGatt.GATT_SUCCESS) {//if(D) Log.i(TAG, "onServicesDiscovered success.");mBluetoothGatt = gatt;BluetoothGattService service = gatt.getService(UUID_SERVICE);if (service == null) {close();return;}//获取对应的特征值mReadCharacteristic = service.getCharacteristic(UUID_READ);mWriteCharacteristic = service.getCharacteristic(UUID_WRITE);mNotifyCharacteristic = service.getCharacteristic(UUID_NOTIFY);//开启mNotifyCharacteristic特征的通知gatt.setCharacteristicNotification(mNotifyCharacteristic, true);}}//读操作回调@Overridepublic void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicRead(gatt, characteristic, status);}//写操作的回调@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);//                            Log.i(TAG,"onCharacteristicWrite:"+status);if (characteristic.getUuid().equals(UUID_WRITE)) {if (status == BluetoothGatt.GATT_SUCCESS) {synchronized (mCallBacks){for (BleAdapterCallBack callBack : mCallBacks) {callBack.onDataSendOk(true);}}} else {synchronized (mCallBacks){for (BleAdapterCallBack callBack : mCallBacks) {callBack.onDataSendOk(false);}}}}}//接收到连接设备传送过来的数据的回调@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {super.onCharacteristicChanged(gatt, characteristic);byte[] data = characteristic.getValue();   //取出接收到的数据if (characteristic.getUuid().equals(UUID_NOTIFY)) {Message message = Message.obtain();message.obj=data;message.what=RECEIVE_DATA;mHandler.sendMessage(message);}}
}//连接设备
public void connect(BluetoothDevice  device)if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.M) {mBluetoothGatt = device.connectGatt(MainActivity.this,false, gattCallback, BluetoothDevice.TRANSPORT_LE);} else {mBluetoothGatt = device.connectGatt(MainActivity.this,false, gattCallback);}
}   /**
* 发送数据
* @param data
*/
protected void writeData(byte[] data) {if (mWriteCharacteristic!=null){mWriteCharacteristic.setValue(data);mBluetoothGatt.writeCharacteristic(mWriteCharacteristic);}
}/**
* 从远程设备读取请求的特征(比较少用)
* @param data
*/
private boolean readData() {return mBluetoothGatt.readCharacteristic(mReadCharacteristic);}

在这里有几个需要注意的问题:
首先,在连接成功之后记得调用gatt.discoverServices(),否则不会回调onServicesDiscovered(),则无法建立连接;其次,在进行连接时系统提供了两个不同的方法:

public BluetoothGatt connectGatt(Context context, boolean autoConnect,BluetoothGattCallback callback) {return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO));}public BluetoothGatt connectGatt(Context context, boolean autoConnect,BluetoothGattCallback callback, int transport) {return (connectGatt(context, autoConnect, callback, transport, PHY_LE_1M_MASK));}

connectGatt(Context context, boolean autoConnect,BluetoothGattCallback callback, int transport)这个方法在6.0之前的隐藏的,6.0才开始开放,两个方法只差一个transport参数,这个参数主要用来设置连接模式,对于这个参数系统提供了三个常量:

BluetoothDevice.TRANSPORT_AUTO:对于GATT连接到远程双模设备无物理传输优先。
BluetoothDevice.TRANSPORT_BREDR:GATT连接到远程双模设备优先BR/EDR。
BluetoothDevice.TRANSPORT_LE:GATT连接到远程双模设备优先BLE。

我们这是Ble设备,所以我们选择BluetoothDevice.TRANSPORT_LE模式。

容易碰到的一些错误和问题
  • 连接不成功,Log提示“BluetoothGatt status 133”

问题:连接总是不成功,onConnectionStateChange()方法中的status 为133。

    出现这个问题的主要的主要原因之一是在连接失败或者连接断开后没有调用gatt.close()进行资源的释放,所以要注意gatt资源的及时释放;另外在6.0及以上的设备连接时记得使用可以设置连接模式的方法,可以大概率降低这个133问题出现的概率。

  • 单条数据大小超出20个byte

    虽然在制定协议的时候会在保证安全性的前提下尽量简洁,但是有时候可能会有那么几条通信指令的数据量超出20个byte,毕竟包序号、校验值等都要占用几个字节,这种时候这么办呢?
    碰到这种情况我们就必须对数据进行分包传送接收:可以和制定协议的人约定每条数据的开头标志、分包数量、结束标志等,这样就可以很清楚的知道当前是不是第一条数据以及后面还有没有数据等。

总结

我们再来回顾一下主要流程:

    获取BluetoothAdapter->开启蓝牙->扫描指定类型设备->设备连接->获取gatt和相关Characteristic,基本的流程就是这些,大家可以根据自己的需求将整个流程封装到一个工具类中,方便使用。

文章末尾给大家推荐一个不错的蓝牙调试工具nRF Connect,该工具可以很方便的查看到连接设备的详细信息,包括Service信息、各个Characteristic的信息和属性等等。

蓝牙开发(一)----- 基于蓝牙Ble的Android应用开发相关推荐

  1. 基于Eclipse平台的Android OpenCV开发环境搭建

    基于Eclipse平台的Android OpenCV开发环境搭建 作者:雨水, 日期:2016-1-31,CSDN博客:http://blog.csdn.net/gobitan 摘要:本文主要记录了如 ...

  2. autocad python二次开发_基于Python AutoCAD ActiveX 二次开发,pyautocad应用技术

    AutoCAD应用程序开发系列 基于Python AutoCAD ActiveX 二次开发技术 主要库:pyautocad==0.2.0 内容#:基于Python AutoCAD ActiveX 二次 ...

  3. 基于经典蓝牙的安卓蓝牙APP开发(基于蓝牙2.0开发,例:HC-05)

    基于经典蓝牙的安卓蓝牙开发-串口 一.展现广播的三种方式 1.通知: 2.对话框: 3.消息提示框 2.在使用Android蓝牙适配器中的startDiscovery需要先打开定位服务 3.在连接蓝牙 ...

  4. AndroidStudio_安卓原生开发_蓝牙扫描设备_另一种方法---Android原生开发工作笔记145

    下面的一个方法是之前写的,但是那种方法有时候会有扫描不到的情况,现在再写一种,这种方法,更简单有效一些. AndroidStudio安卓原生开发_Android扫描附近指定的蓝牙设备_通过设备名称过滤 ...

  5. Android 开发 -- 开发第一个安卓程序、Android UI开发(布局的创建:相对布局和线性布局、控件单位:px pt dp sp、常用控件 、常见对话框、ListView)

    文章目录 1. 开发第一个Hello World程序 1.1 开发程序 1.2 认识程序中的文件 1.3 Android程序结构 1.4 安卓程序打包 2. Android UI开发 2.1 布局的创 ...

  6. android开发培训大纲,华图教育-Android应用开发培训教学大纲

    Android应用开发工程师课程大纲 阶段一:Java语言和编程(25天) 目标: Java语言是Android应用开发的基础.本课程将从基本的Java语法开始,并通过一个SuperQQ项目,让学员掌 ...

  7. libgdx开发指南_使用libgdx进行Android游戏开发–一天中的原型,第1a部分

    libgdx开发指南 在本文中,我将绕开游戏引擎和组件的构建模块,并演示如何使用libgdx库快速制作游戏原型. 您将学到什么: 创建一个非常简单的2D Shooter Platformer游戏. 完 ...

  8. mui开发项目流程_【经验分享】用HBuilder开发的基于MUI和H5+的APP开发及上架经历...

    一.写在前面 2017年,个人最大的收获,是第一次完成了这一款APP的开发并顺利上架,同时获得了还算可观的收益. 这是我前公司的项目,公司的主营业务是旅游,并不是什么科技公司,我之前一直在公司任职技术 ...

  9. c# 无法加载oraops.dll_Robotstudio软件二次开发:基于C#语言的Smart组件开发基础

    Robotstudio软件除了支持Add-Ins插件的二次开发以外,还支持Smart组件的二次开发.开发语言同样是基于.NET框架的C#语言或VB语言.Smart组件是Robotstudio软件中实现 ...

最新文章

  1. 零基础可以学python吗-python零基础能学吗
  2. Dagger2 知识梳理(1) Dagger2 依赖注入的两种方式
  3. k8s,nginx备份日志脚本
  4. 基于某网站的信息爬取与保存_指定查询内容
  5. java zip文件操作,java 关于 zip 文件 的 基本操作
  6. java jsf_使用Java和JSF构建一个简单的CRUD应用
  7. 【Linux】Linux下使用w命令和uptime命令查看系统负载
  8. ThinkPHP实现登陆功能
  9. Java 高级数据结构 —— Properties
  10. 命令提示符使用java 类报错_lt;03gt;详解第一个Java程序
  11. 塑胶产品规格书范本_塑胶产品结构设计--卡扣 - 范文中心
  12. 记一次 jenkins 构建失败 “Cannot find module ‘core-js/modules/es.promise.finally‘”
  13. PCL中点云配准精通级实例解析
  14. python语音识别库kaldi_Kaldi 使用 DFSMN 训练语音模型
  15. 个人作业 Alpha项目测试
  16. 极客大挑战 2021
  17. Win10电脑怎么恢复出厂设置
  18. CALayer创建图层(转)
  19. 各大AI研究院共35场NLP算法岗面经奉上
  20. DOM初探(18)——让滚动条滚动

热门文章

  1. power designer概述
  2. 入职趣事之银行短信验证码收不到咋办
  3. SpringBoot+Vue实现个人信息以及头像数据联动
  4. 画论84 郑燮《板桥题画兰竹》
  5. GB2312汉字拼音对照表 1
  6. fuckporn!一键采集色情网址并自动识别验证码向12321举报!
  7. Resnet50的参数计算
  8. jPage.js分页
  9. rac管理 sqlserver_Oracle RAC管理及维护命令详解
  10. C# 的EventHandler 实际使用例子