前段时间,项目要接入一个ble硬件,以前也没接触过ble开发,在查阅不少资料和踩了不少坑才完成任务,因此打算写一个简单的ble开发步骤,希望能帮助到初次接触ble开发的同学。

BLE相关术语简介

GATT:GATT 的全名是 Generic Attribute Profile(姑且翻译成:普通属性协议),它定义两个 BLE 设备通过叫做 Service 和 Characteristic 的东西进行通信。

Profile:Profile 并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。

Service:Service 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。

Characteristic:在 GATT 事务中的最低界别的是 Characteristic,Characteristic 是最小的逻辑数据单元,与 Service 类似,每个 Characteristic 用 16 bit 或者 128 bit 的 UUID 唯一标识。在 Android 开发中,建立蓝牙连接后,通过蓝牙发送数据给外围设备就是往这些 Characteristic 中的 Value 字段写入数据;外围设备发送数据给手机就是监听这些 Charateristic 中的 Value 字段有没有变化,如果发生了变化,手机的 BLE API 就会收到一个监听的回调。
下面是一张来自官网的结构图

更多关于BLE GATT介绍可查看以下链接
BLE GATT介绍
GATT PROFILE 介绍

Android BLE开发相关API介绍

BluetoothAdapter
BluetoothAdapter 拥有基本的蓝牙操作,例如开启蓝牙扫描,使用已知的 MAC 地址 (BluetoothAdapter#getRemoteDevice)实例化一个 BluetoothDevice 用于连接蓝牙设备的操作等等。

BluetoothDevice
代表一个远程蓝牙设备。这个类可以连接所代表的蓝牙设备或者获取一些有关它的信息,例如它的名字,地址和绑定状态等等。

BluetoothGatt
这个类提供了 Bluetooth GATT 的基本功能。例如重新连接蓝牙设备,发现蓝牙设备的 Service 、读写ble设备等等。

BluetoothGattService
对应前文所介绍的Service,通过 BluetoothGatt.getService 获得,通过这个类的 getCharacteristic(UUID uuid) 进一步获取 Characteristic 实现蓝牙数据的双向传输。

BluetoothGattCharacteristic
对应前文提到的 Characteristic。对ble设备的读写主要通过这个类来完成,也是我们主要打交道的类。

BLE设备接入开发

权限

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>  

连接设备

//先去获取BluetoothAdapterprivate BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();//检查手机蓝牙开关、是否支持bleprivate void checkBluetooth() {//是否支持蓝牙功能if (mBluetoothAdapter == null) {return;}//是否支持BLEif (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {Toast.makeText(mContext, "不支持BLE功能", Toast.LENGTH_SHORT).show();return;}//是否打开蓝牙开关if (!mBluetoothAdapter.isEnabled()) {Toast.makeText(mContext, "请打开蓝牙开关", Toast.LENGTH_SHORT).show();return;}//搜索设备scanBleDevice(true);}
   /*** 搜索ble设备* @param enable 开始搜索或停止搜索*/private void scanBleDevice(final boolean enable) {if (enable) {//开始搜索设备,搜索到设备会执行回调接口mLeScanCallbackmBluetoothAdapter.startLeScan(mLeScanCallback);isScanning = true;//搜索设备十分耗电,应该避免长时间搜索,这里设置10s超时停止搜索mHandler.postDelayed(new Runnable() {@Overridepublic void run() {scanBleDevice(false);Toast.makeText(mContext, "搜索超时,请重试", Toast.LENGTH_SHORT).show();}}, 10 * 1000);} else {mBluetoothAdapter.stopLeScan(mLeScanCallback);isScanning = false;}}
 /*** 设备搜索回调*/private BluetoothAdapter.LeScanCallback mLeScanCallback =new BluetoothAdapter.LeScanCallback() {@Overridepublic void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {//回调不是在ui线程中执行的,但是ble设备的连接、断开最好在ui线程中执行,否则可能会出现些奇奇怪怪的问题((Activity)mContext).runOnUiThread(new Runnable() {@Overridepublic void run() {//判断设备是否是我们要找的if (!TextUtils.isEmpty(device.getName()) && device.getName().equals("AP-002")) {//找到设备后停止搜索,并取消开始搜索时设置的超时取消搜索mHandler.removeCallbacksAndMessages(null);mBluetoothAdapter.stopLeScan(mLeScanCallback);if (isScanning) {//开始连接设备connect(device.getAddress());isScanning = false;}}}});}};
    //连接设备private boolean connect(final String address) {if (mBluetoothAdapter == null || TextUtils.isEmpty(address)) {Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");return false;}//获取设备final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);if (device == null) {Log.d(TAG, "Device not found.  Unable to connect.");return false;}//连接设备,连接成功或失败都会执行回调mGattCallback的//onConnectionStateChange(BluetoothGatt gatt, int status, int newState)方法//mGattCallback是mBluetoothGatt操作的回调//包括读、写、连接、断开等操作,是与ble设备通信十分重要的一部分mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);Log.d(TAG, "Trying to create a new connection.");return true;}

BluetoothGattCallback 有很多回调方法,我们只重写几个常用的方法

 // **回调接口是在非ui线程回调**private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {super.onCharacteristicChanged(gatt, characteristic);//设备发送数据回调,由于ble设备可能有有多个characteristic//用于发送数据,这里进行比较,确认是否是我们想要的if (characteristic.equals(mReceiveCharac)) {//提取设备发送的数据Log.d(TAG, "接收到设备发送的数据:"+characteristic.getValue()); }}@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);//mBluetoothGatt.DiscoverServices()的回调Log.d(TAG, "discovery service successfully");//以列表形式返回List<BluetoothGattService> mGattServices;mGattServices = gatt.getServices();//筛选保存我们需要的characteristic和servicedisplayGattServices(mGattServices);}@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);//手机往ble设备写数据回调if (status == BluetoothGatt.GATT_SUCCESS && characteristic.equals(mWriteCharac)) {//写入的数据可以通过characteristic.getValue()获取,用于确认是否是之前写入的数据//这里以一个关机命令为例if (characteristic.getValue().equals(shutDownCommand)) {mBluetoothGatt.disconnect();mBluetoothGatt.close();mBluetoothGatt = null;Log.d(TAG, "写入关机命令成功");}}}@Overridepublic void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic,int status) {super.onCharacteristicRead(gatt, characteristic, status);//读设备回调,这里以读取设备电池状态为例if (status == BluetoothGatt.GATT_SUCCESS && characteristic.equals(mBatteryCharac)) {characteristic.getValue();}}@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {super.onConnectionStateChange(gatt, status, newState);//连接状态回调//连接或断开连接操作是否成功if (status == BluetoothGatt.GATT_SUCCESS) {if (newState == BluetoothProfile.STATE_CONNECTED) {//连接操作成功,获取service列表gatt.discoverServices();} else {//断开连接操作成功Log.d(TAG, "断开连接成功");Toast.makeText(mContext, "设备已断开", Toast.LENGTH_SHORT).show();mBluetoothGatt.close();mBluetoothGatt = null;return;}} else {if (newState == BluetoothProfile.STATE_DISCONNECTED ) {//连接失败,重连//这里之所以连接失败还要先断开再重连//是因为不知为何,有时连接成功后,系统又回调到这里//如果不断开连接再重连的话会出问题mBluetoothGatt.disconnect();mBluetoothGatt.close();mBluetoothGatt = null;mHandler.post(new Runnable() {@Overridepublic void run() {reconnectDevice();}});Log.d(TAG, "连接失败重连");}}}};
//重连设备public void reconnectDevice() {if (mBluetoothGatt == null) {checkBluetooth();Toast.makeText(mContext, "开始搜索连接设备", Toast.LENGTH_SHORT).show();} else {Toast.makeText(mContext, "设备已连接", Toast.LENGTH_SHORT).show();}}
/*** 遍历设备所有service和characteristic* 一般我们再开发接入ble设备时,硬件那边会给出ble设备文档* 根据文档所说的service、characteristic的uuid进行读写操作* 保存所需characteristic* 设置用于接收设备数据的characteristic notification属性* @param gattServices*/private void displayGattServices(List<BluetoothGattService> gattServices) {Log.d(TAG, Thread.currentThread()+""+"display service and characteristics");if (gattServices == null) {return;}for (BluetoothGattService gattService : gattServices) { // 遍历出gattServices里面的所有服务//比较service的uuid,只取所需的serviceif (gattService.getUuid().equals(UUID.fromString(BATTERY_SERVICE_UUID))|| gattService.getUuid().equals(UUID.fromString(PRIVATE_SERVICE_UUID))) {List<BluetoothGattCharacteristic> gattCharacteristics =gattService.getCharacteristics();Log.d(TAG, "service uuid: " + gattService.getUuid());for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { //// 遍历每条服务里的所有Characteristic,保存所需CharacteristicsLog.d(TAG, "characteristics uuid: " + gattCharacteristic.getUuid());if (gattCharacteristic.getUuid().toString().equalsIgnoreCase(BATTERY_CHARACTERISTICS_UUID)) {//读数据characteristic,这里以电池characteristic为例mBatteryCharac = gattCharacteristic;                       } else if (gattCharacteristic.getUuid().toString().equalsIgnoreCase(RECEIVE_CHARACTERISTICS_UUID)) {//接收数据characteristicmReceiveCharac = gattCharacteristic;//设置notification通知,按键回调在onCharacteristicChanged//以下代码一定不能少,否则将无法接收到设备发送的数据boolean isEnableNotification = mBluetoothGatt.setCharacteristicNotification(mReceiveCharac, true);if (isEnableNotification) {List<BluetoothGattDescriptor> descriptorList =mReadKeypressCharac.getDescriptors();if (descriptorList != null && descriptorList.size() > 0) {for (BluetoothGattDescriptor descriptor : descriptorList) {descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);mBluetoothGatt.writeDescriptor(descriptor);}}}} else if (gattCharacteristic.getUuid().toString().equalsIgnoreCase(WRITE_DATA_UUID)) {//写数据characteristicmWriteCharac = gattCharacteristic;}}}}}

断开连接

   /*** 断开连接,回调mGattCallback* */public void disconnectDevice() {if (mBluetoothGatt != null) {mBluetoothGatt.disconnect();  }}

数据读、写、接收

对设备的读、写、接收都会在mGattCallback中回调
硬件工程师在开发ble设备时,会设置不同的characteristic,并设置其读、写、通知等属性,只有具有这些属性,才可以对characteristic进行相应的读写等操作。
这里说明下,读数据和接收数据是不一样的,读数据需要我们主动操作,通过BluetoothGatt#readCharacteristic(),发起读操作,然后在回调方法里接收数据;而接收数据是ble设备主动发送数据,系统的BLE框架会回调onCharacteristicChanged(),我们只需在这个方法里提取数据即可。
当我们要接入ble设备时,要根据ble设备的文档,对指定characteristic进行指定读、写、接收等操作,才能实现正确的通信。
**注意:characteristic的读、写操作都是串行的,也就是说,只有前一个读写操作回调成功后才会执行下一个操作,
若是在上一个读写操作还没回调便进行下一个操作,那么这个操作会被ble设备抛弃掉。**

读数据

   /**这里以本人手头上ble设备读取电池状态为例*不同的characteristic作用不同,要根据硬件接入文档,*选择正确的characteristic进行操作* 获取电池状态*/private void getBatteryInfo() {if (mBluetoothGatt != null && mBatteryCharac != null) {mBluetoothGatt.readCharacteristic(mBatteryCharac);Log.d(TAG, "读取电量");}}

接着在回调方法里取出数据

 @Overridepublic void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic,int status) {super.onCharacteristicRead(gatt, characteristic, status);//读设备回调,这里以读取设备电池状态为例if (status == BluetoothGatt.GATT_SUCCESS && characteristic.equals(mBatteryCharac)) {//取出数据characteristic.getValue();}}

写数据

在通过指定characteristic向ble设备写入数据时,一次性只能写入20字节的数据,当需要写入的数据超过20字节时就要分次写入。
一般,ble设备会要求我们写入一些固定的数据,来作为ble识别的命令
这里,以一个让ble关机的命令为例

//关闭设备命令,20字节private byte[] shutDownCommand = new byte[]{0x21, 0x10, 0x26, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, (byte) 0xFF};/*** 写入关闭设备命令*/public void shutdownDevice() {if (mBluetoothGatt != null && mWriteDataCharac != null) {Log.d(TAG, "关闭设备");mWriteCharac.setValue(shutDownCommand);mWriteCharac.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);mBluetoothGatt.writeCharacteristic(mWriteDataCharac);           }}    

接着回调

  @Overridepublic void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);//写入命令成功,且characteristic是我们所写入的characteristicif (status == BluetoothGatt.GATT_SUCCESS && characteristic.equals(mWriteCharac)) {//写入的数据可以通过characteristic.getValue()获取,用于确认是否是之前写入的数据if (characteristic.getValue().equals(shutDownCommand)) {mBluetoothGatt.disconnect();mBluetoothGatt.close();mBluetoothGatt = null;Log.d(TAG, "写入关机命令成功");}}}

接收数据

对于接收ble设备数据,手机是被动接收的。也就是说,当设备通过指定characteristic发送数据时,系统会回调BlutoothGatt#onCharacteristicChanged(),在这个回调方法里通过characteristic.getValue()来获取设备发送的数据。但是,能够接收到数据的前提是对characteristic设置通知属性,前面在筛选保存service和characteristic时候,我们进行这么一个操作

f (gattCharacteristic.getUuid().toString().equalsIgnoreCase(RECEIVE_CHARACTERISTICS_UUID)) {//接收数据的characteristicmReceiveCharac = gattCharacteristic;//设置notification通知属性,按键回调在onCharacteristicChanged//以下代码一定不能少,否则将无法接收到设备发送的数据boolean isEnableNotification = mBluetoothGatt.setCharacteristicNotification(mReceiveCharac, true);if (isEnableNotification) {List<BluetoothGattDescriptor> descriptorList =mReadKeypressCharac.getDescriptors();if (descriptorList != null && descriptorList.size() > 0) {for (BluetoothGattDescriptor descriptor : descriptorList) {descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);mBluetoothGatt.writeDescriptor(descriptor);}}}}

对设备要发送数据的characteristic设置notification属性,并对该characteristic下的descriptor设置BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE。这样,才能够成功回调onCharacteristicChanged()方法并接收数据
下面以接收ble设备按键按下时发送数据为例

//对于接收数据,我们无需做什么操作,只需要这回调方法里获取数据即可@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {super.onCharacteristicChanged(gatt, characteristic);//设备发送数据回调,由于ble设备可能有有多个characteristic//用于发送数据,这里进行比较,确认是否是我们想要的if (characteristic.equals(mReceiveCharac)) {//提取设备发送的数据Log.d(TAG, "接收到设备发送的数据:"+characteristic.getValue()); }}

总结

以上便是ble设备接入的基本流程和读写接收等操作,一些容易踩到的坑和重点在注释中已经写地很罗嗦,有不明白的可以在文章下留言,一起探讨,希望这篇文章能对大家有所帮助。

Android ble开发详解相关推荐

  1. Android 蓝牙BLE开发详解

    Android 蓝牙BLE开发详解 由于年初接手了个有关蓝牙BLE的项目,开始了对蓝牙ble的学习,经过长时间的慢慢学习(学得太慢,太拖了),终于了解了该怎么写蓝牙BLE,现在就给大家分享一下. 一. ...

  2. 《Android游戏开发详解》——第1章,第1.6节函数(在Java中称为“方法”更好)...

    本节书摘来自异步社区<Android游戏开发详解>一书中的第1章,第1.6节函数(在Java中称为"方法"更好),作者 [美]Jonathan S. Harbour,更 ...

  3. JMessage Android 端开发详解

    JMessage Android 端开发详解 目前越来越多的应用会需要集成即时通讯功能,这里就为大家详细讲一下如何通过集成 JMessage 来为你的 App 增加即时通讯功能. 首先,一个最基础的 ...

  4. 《Android游戏开发详解》一2.16 区分类和对象

    本节书摘来异步社区<Android游戏开发详解>一书中的第2章,第2.16节,作者: [美]Jonathan S. Harbour 译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社 ...

  5. 《Android游戏开发详解》一3.1 构造方法

    本节书摘来异步社区<Android游戏开发详解>一书中的第3章,第3.1节,作者: [美]Jonathan S. Harbour 译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社区 ...

  6. 《Android游戏开发详解》一导读

    前 言 Android游戏开发详解 作为对编程知之甚少或者毫无所知的初学者,开始学习Android游戏开发,可能会觉得就像是穿越陌生的星际的旅程.有太多的事情要尝试,太多的知识要学习,令人遗憾的是,还 ...

  7. Android USB 开发详解

    Android USB 开发详解 先附上 Android USB 官方文档 Android通过两种模式支持各种 USB 外设和 Android USB 附件(实现Android附件协议的硬件):USB ...

  8. 《Android游戏开发详解》——第3章,第3.1节构造方法

    本节书摘来自异步社区<Android游戏开发详解>一书中的第3章,第3.1节构造方法,作者 [美]Jonathan S. Harbour,更多章节内容可以访问云栖社区"异步社区& ...

  9. 《Android游戏开发详解》一2.18 使用Java API中的对象

    本节书摘来异步社区<Android游戏开发详解>一书中的第2章,第2.18节,译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社区"异步社区"公众号查看. 2.1 ...

最新文章

  1. java设计模式---三种工厂模式
  2. Java中几种常量池的区分
  3. JBoss BPM Suite 6.0.3版本的5个实用技巧
  4. zz 聊聊并发(一)
  5. PKU 学生反馈 2009 - 4
  6. BookKeeper总结
  7. ZOJ-3953 Intervals,t
  8. ipad pythonista_iPad编程软件推荐(一) —— Pythonista 3
  9. python实现二十四点
  10. idea php 提示丢失,解决idea 暂存文件或idea切换分支代码丢失的问题
  11. 新高考如何选科?职引教你一招简单又直接的方法
  12. 使用PPT制作倒计时
  13. TCP/IP网络编程 - 基础学习
  14. 怎么修改服务器上的分数,在服务器上设置 WinSAT 分数
  15. HDR视频色调映射算法(之三:Block matching TMO)
  16. 丽江古城历史悠久,古朴自然
  17. ant-design vue input通过那个事件可以获得输入框变化的值_VUE使用百度地图教程
  18. 关于用深度学习做answer selection的论文
  19. FPGA开发板XILINX-K7核心板Kintex7 XC7K325 410T工业级
  20. 报错:Expected comma jsonc(514)

热门文章

  1. c语言上机试题库及答案,《C语言上机试题及答案》.doc
  2. 亚马逊Amazon多账号操作攻略
  3. 更加简便的使用VSS
  4. 你要的摄像头检测来啦
  5. PHP实现简单计算器
  6. 论文投稿指南——中文核心期刊推荐(计算机技术)
  7. 01day入学测试总结
  8. python玩扫雷_Python玩转算法—扫雷
  9. AWD攻防技巧(水文)
  10. 当程序员后,才突然明白的21件事……