我们提到了中央设备(central)和外围设备(peripheral),在这里我们可以这样简单的理解:

中央设备(central):
收到外围设备发出的广播信号后能主动发起连接的主设备,
例如我们给摩拜单车开锁时我们的手机就是作为中央设备连接单车并进行开锁等一系列操作的,
通常情况下同一时间一台中央设备只能与最多7台外围设备建立连接。
外围设备(peripheral):能被中央设备连接的从设备,
同一时间外围设备只能被一个中央设备连接。

:Android从4.3(API Level 18) 开始支持低功耗蓝牙,
但是刚开始只支持作为中央设备(central)模式,
从 Android 5.0(API Level 21) 开始才支持作为外围设备(peripheral)的模式,
因此我们最好使用Android 5.0以上版本的手机进行下面的操作。

  • 外围设备
  • 中央设备

需要的权限
  <uses-permission android:name="android.permission.BLUETOOTH" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

具体操作流程:

首先我们准备2台手机,手机A作为中央设备,手机B作为外围设备,在打开B手机的ble广播后,我们使用A手机进行打开蓝牙-->扫描-->连接-->获取服务,特征-->打开通知-->写特征-->读特征-->断开连接,通过这些步骤我们就能学会Android Ble 的基本方法的使用。

打开蓝牙

打开蓝牙有以下两种方式:

    //方法一BluetoothManager bluetoothManager= (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();if (mBluetoothAdapter != null){mBluetoothAdapter.enable();}
    //方法二BluetoothManager bluetoothManager= (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();if (!mBluetoothAdapter.isEnabled() && !mBluetoothAdapter.isEnabled()) {Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);}

使用方法一将会直接打开蓝牙,使用方法二会跳转到系统Activity由用户手动打开蓝牙

扫描

扫描是一个非常耗电的操作,因此当我们找到我们需要的设备后应该马上停止扫描。官方提供了2个扫描的方法:

  //旧API//启动扫描private void scan(){BluetoothManager bluetoothManager= (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);bluetoothManager.getAdapter().startLeScan(mLeScanCallback);//如果想要指定搜索设备,可以使用下面这个构造方法,传入外围设备广播出的服务的UUID数组UUID[] uuids=new UUID[]{UUID_ADV_SERVER};bluetoothManager.getAdapter().startLeScan(uuids,mLeScanCallback);}//停止扫描private void stopScan(){BluetoothManager bluetoothManager= (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);bluetoothManager.getAdapter().stopLeScan(mLeScanCallback);}//扫描结果回调LeScanCallback mLeScanCallback = new LeScanCallback() {@Overridepublic void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {//device:扫描到的蓝牙设备对象//rssi:扫描到的设备的信号强度,这是一个负值,值越大代表信号强度越大//scanRecord:扫描到的设备广播的数据,包含设备名,服务UUID等}};
  //新API,需要Android 5.0(API Level 21)及以上版本才能使用//启动扫描private void scanNew() {BluetoothManager bluetoothManager= (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);//基本的扫描方法bluetoothManager.getAdapter().getBluetoothLeScanner().startScan(mScanCallback);//设置一些扫描参数ScanSettings settings=new ScanSettings.Builder()//例如这里设置的低延迟模式,也就是更快的扫描到周围设备,相应耗电也更厉害.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();//你需要设置的过滤条件,不只可以像旧API中的按服务UUID过滤//还可以按设备名称,MAC地址等条件过滤List<ScanFilter> scanFilters=new ArrayList<>();//如果你需要过滤扫描到的设备可以用下面的这种构造方法bluetoothManager.getAdapter().getBluetoothLeScanner().startScan(scanFilters,settings,mScanCallback);}//扫描结果回调ScanCallback mScanCallback = new ScanCallback() {@Overridepublic void onScanResult(int callbackType, ScanResult result) {//callbackType:扫描模式//result:扫描到的设备数据,包含蓝牙设备对象,解析完成的广播数据等}};//停止扫描private void stopNewScan(){BluetoothManager bluetoothManager= (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);bluetoothManager.getAdapter().getBluetoothLeScanner().stopScan(mScanCallback);}

相比旧API,新API的功能更全面,但是需要Android 5.0以上才能使用,究竟需要使用哪种方法,大家可以根据自己的实际情况选择。

注意:

  • 1.如果搜索不到设备,请检查对于Android 6.0及以上版本ACCESS_COARSE_LOCATION或者ACCESS_FINE_LOCATION权限是否已经动态授予,同时检查位置信息(也就是GPS)是否已经打开,一般来说搜不到设备就是这两个原因。

  • 2.不管是新旧API的扫描结果回调都是不停的回调扫描到的设备,就算是相同的设备也会重复回调,直到你停止扫描,因此最好不要在回调方法中做过多的耗时操作,否则可能会出现这个问题,如果需要处理回调的数据可以把数据放到另外一个线程处理,让回调尽快返回。

连接

同一时间我们只能对一个外围设备发起连接,如果需要对多个设备连接可以等上一个连接成功后再进行下一个连接,否则如果前面的某个连接操作失败了没有回调,后面的操作会被一直阻塞。

  //发起连接private void connect(BluetoothDevice device){mBluetoothGatt = device.connectGatt(context, false, mBluetoothGattCallback);}//Gatt操作回调,此回调很重要,后面所有的操作结果都会在此方法中回调BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {//gatt:GATT客户端//status:此次操作的状态码,返回0时代表操作成功,返回其他值就是各种异常//newState:当前连接处于的状态,例如连接成功,断开连接等//当连接状态改变时触发此回调}@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {//gatt:GATT客户端//status:此次操作的状态码,返回0时代表操作成功,返回其他值就是各种异常//成功获取服务时触发此回调,“获取服务,特征”一节会介绍}@Overridepublic void onCharacteristicRead(BluetoothGatt gatt,final BluetoothGattCharacteristic characteristic, final int status) {//gatt:GATT客户端//status:此次操作的状态码,返回0时代表操作成功,返回其他值就是各种异常//characteristic:被读的特征//当对特征的读操作完成时触发此回调,“读特征”一节会介绍}@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt,final BluetoothGattCharacteristic characteristic, final int status) {//gatt:GATT客户端//status:此次操作的状态码,返回0时代表操作成功,返回其他值就是各种异常//characteristic:被写的特征//当对特征的写操作完成时触发此回调,“写特征”一节会介绍}@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt,final BluetoothGattCharacteristic characteristic) {//gatt:GATT客户端//status:此次操作的状态码,返回0时代表操作成功,返回其他值就是各种异常//characteristic:特征值改变的特征//当特征值改变时触发此回调,“打开通知”一节会介绍}@Overridepublic void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,int status) {//gatt:GATT客户端//status:此次操作的状态码,返回0时代表操作成功,返回其他值就是各种异常//descriptor:被读的descriptor//当对descriptor的读操作完成时触发}@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,int status) {//gatt:GATT客户端//status:此次操作的状态码,返回0时代表操作成功,返回其他值就是各种异常//descriptor:被写的descriptor//当对descriptor的写操作完成时触发,“打开通知”一节会介绍}};

当我们调用connectGatt方法后会触发onConnectionStateChange这个回调,回调中的status我们用来判断这次操作的成功与否,newState用来判断当前的连接状态。

注意:

我们在调用连接和断开连接这两方法的时候最好放到主线程调用,否则可能会在一些手机上遇到奇怪的问题

获取服务,特征

当我们连接成功后,GATT客户端(手机A)可以通过发现方法检索GATT服务端(手机B)的服务和特征,以便后面操作使用。

  //连接成功后掉用发现服务gatt.discoverServices();//当服务检索完成后会回调该方法,检索完成后我们就可以拿到需要的服务和特征@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {//获取特定UUID的服务BluetoothGattService service = gatt.getService(UUID_SERVER);//获取所有服务List<BluetoothGattService> services = gatt.getServices();if (service!=null){//获取该服务下特定UUID的特征mCharacteristic = service.getCharacteristic(UUID_CHARWRITE);//获取该服务下所有特征List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();}}

打开通知
打开通知官方的标准做法分两步:

//官方文档做法
private BluetoothGatt mBluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
...
//第一步,开启手机A(本地)对这个特征的通知
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
...
//第二步,通过对手机B(远程)中需要开启通知的那个特征的CCCD写入开启通知命令,来打开通知
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);

由于Android7.0以前版本存在一个bug:对descriptor的写操作会复用父特征的写入类型,这个bug在7.0之后进行了修复,为了提高兼容性,我们可以对官方做法稍许修改:

private BluetoothGatt mBluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
...
//第一步,开启手机A(本地)对这个特征的通知
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
...
//第二步,通过对手机B(远程)中需要开启通知的那个特征的CCCD写入开启通知命令,来打开通知
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
//获取特征的写入类型,用于后面还原
int parentWriteType = characteristic.getWriteType();
//设置特征的写入类型为默认类型
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
//还原特征的写入类型
characteristic.setWriteType(parentWriteType);

接下来我们来看看回调

      @Overridepublic void onCharacteristicChanged(BluetoothGatt gatt,final BluetoothGattCharacteristic characteristic) {//当手机B的通知发过来的时候会触发这个回调}@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,int status) {//第二步会触发此回调}

对于有的设备可能我们只需要执行第一步就能收到通知,但是为了保险起见我们最好两步都做,以防出现通知开启无效的情况。
再次强调读、写、通知等这些GATT的操作都只能串行的使用,并且在执行下一个任务前必须保证上一个任务已经完成并且成功回调,否则可能出现后面的任务都阻塞无法进行的情况。
对于开启通知这个操作触发onDescriptorWrite时代表任务完成,可以进行下一个GATT操作。

写特征:

//默认的写入类型,需要外围设备响应
mCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
//无需设备响应的写入类型
mCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);mCharacteristic.setValue(data);
mBluetoothGatt.writeCharacteristic(mCharacteristic);//写入特征回调@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt,final BluetoothGattCharacteristic characteristic, final int status) {}

写特征的用法和前面打开通知中的写descriptor类似。

注意:

上面提到了2种写入类型,他们的区别是:

  • WRITE_TYPE_DEFAULT:写入数据后需要外围设备给出响应才会回调onCharacteristicWrite
  • WRITE_TYPE_NO_RESPONSE:写入数据后无需外围设备给出响应就会回调onCharacteristicWrite
  • 一次写入最多能写入20字节的数据,如果需要写入更多的数据可以分包多次写入,或者如果设备支持更改MTU的话一次最多可以传输512字节。

读特征:

//读特征
mBluetoothGatt.readCharacteristic(mCharacteristic);//读特征的回调
@Override
public void onCharacteristicRead(BluetoothGatt gatt,final BluetoothGattCharacteristic characteristic, final int status) {}

读特征这个操作没多少坑,只是需要前面提到的成功回调以后才算执行完成

断开连接

private void disConnect(){if (mBluetoothGatt!=null){//断开连接mBluetoothGatt.disconnect();// mBluetoothGatt.close();}}@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {if (newState==BluetoothProfile.STATE_DISCONNECTED){//关闭GATT客户端gatt.close();}
}

注意:

  • 断开连接连接一样最好都在主线程执行
  • BluetoothGatt.disConnect()方法和BluetoothGatt.close()方法要成对配合使用,有一点需要注意:如果调用disConnect()方法后立即调用close()方法(就像上面注释掉的代码那样)蓝牙能正常断开,只是在onConnectionStateChange中我们就收不到newState为BluetoothProfile.STATE_DISCONNECTED的状态回调,因此,可以在收到断开连接的回调后在关闭GATT客户端。
  • 如果断开连接后没调用close方法,在多次重复连接-断开之后可能你就再也连不上设备了。

NordicSemiconductor官方的Android ble库(Android-BLE-Library):https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2FNordicSemiconductor%2FAndroid-BLE-Library

BLElib开源:https://github.com/NoHarry/BLELib

Android BLE新手进入相关推荐

  1. Android基础新手教程——1.5.2 Git之使用GitHub搭建远程仓库

    Android基础新手教程--1.5.2 Git之使用GitHub搭建远程仓库 标签(空格分隔): Android基础新手教程 本节引言: 在上一节中.我们学习了怎样使用Git.构建我们的本地仓库.轻 ...

  2. android手机上的ancs,Android BLE开发之操作IOS ANCS

    前言 之前写过两篇有关于ANCS的文章,最近一段时间老是有人问关于得到ANCS服务的问题,因为IOS ANCS不同于其他的Peripheral一样对周边所有的蓝牙设备广播自己,而是仅有连接上配对并连接 ...

  3. Android BLE学习(一): Android搜索BLE设备

    http://my.csdn.net/lidec 背景 总结一下最近ble的学习情况.自从入手ble 51822开发板后就开始不停加班,中途出于好奇,业余时间写了一些单片机上json解析相关的东西,妄 ...

  4. android BLE Peripheral 手机模拟设备发出BLE广播 BluetoothLeAdvertiser

    android 从4.3系统开始可以连接BLE设备,这个大家都知道了.iOS是从7.0版本开始支持BLE.android 进入5.0时代时,开放了一个新功能,手机可以模拟设备发出BLE广播, 这个新功 ...

  5. Android BLE蓝牙详细解读

    代码地址如下: http://www.demodashi.com/demo/15062.html 随着物联网时代的到来,越来越多的智能硬件设备开始流行起来,比如智能手环.心率检测仪.以及各式各样的智能 ...

  6. 快速接入 Android BLE 开发的基础框架

    代码地址如下: http://www.demodashi.com/demo/12092.html * Android BLE基础操作框架,基于回调,操作简单.包含扫描.多连接.广播包解析.服务读写及通 ...

  7. Android BLE(3) ---FastBle解析

    Android BLE开发详解和FastBle源码解析 项目中有用到了蓝牙相关的功能,所以之前也断断续续地针对蓝牙通信尤其是BLE通信进行了一番探索,整理出了一个开源框架FastBle与各位分享经验. ...

  8. Android BLE(1)---蓝牙通讯学习

    Android BLE蓝牙通讯学习 在app应用的开发过程中,一般和蓝牙接触的不多,但是随着智能穿戴设备的发展,穿戴设备和手机关联的app越来越多,之前也是没怎么接触过这一块的东西,正好最近需要做一个 ...

  9. Android 系统(252)---Android:BLE智能硬件开发详解

    Android:BLE智能硬件开发详解 目录 前言 BLE是个什么鬼 BLE中的角色分工 主要的关键词和概念  GATT(Generic Attribute Profile ) Characteris ...

最新文章

  1. python之commands模块
  2. 平板就是生产力?东京大学研究者“辟谣”了,用纸笔记录,更有利于记忆
  3. 网站内容优化时需注意哪些事项?
  4. Android:相对布局综合小演练—智能家居,按键快速美化的小技巧
  5. Git的SourceTree添加授权添加用户名与密码
  6. 64位以内Rabin-Miller 强伪素数测试和Pollard rho 因数分解解析
  7. 如何才能优雅地书写JS代码
  8. 学生使用计算机中怎么关机,学会正确开关机初中计算机教案
  9. 投稿过程要不要考虑预印本?——medRxiv那些事
  10. f5在运营商计费系统中的版本升级割接详细步骤
  11. 如何用英文向论文作者索要源代码--邮件模板
  12. c++超详细基础教程(快速入门)
  13. 一文掌握步进电机控制
  14. 【技巧总结】理解XXE从基础到盲打
  15. 电脑计算机c盘打不开怎么办,电脑的c盘炸了打不开电脑了怎么处理
  16. python求助神器_【python从零开始(被称之为神器的装饰器)】- 环球网校
  17. 【WinForm】关于截图识别数字并计算的桌面程序实现方案
  18. 初步学习软件测试的一些思考
  19. MoveIT和KDL中进行机械臂位置和姿态插值
  20. 计算机安全凭据,4776 (S、F) 计算机尝试验证帐户的凭据。 (Windows 10) - Windows security | Microsoft Docs...

热门文章

  1. 数据结构 | 链表队列(基本操作及图示)
  2. css实现文字太长,显示省略号
  3. 扩展单元格 == 报表
  4. jsp 中select 下拉选择框 el 三元运算符 如何选中与不选中
  5. 举例说明操作系统在计算机系统中的重要地位,第一二三章作业参考答案
  6. php重定向mysql_使用.php文件生成MySQL转储
  7. mysql 默认时间_使用Sysbench对滴滴云MySQL进行基准测试
  8. 惊喜!想要高清背景壁纸图片素材,看这里
  9. 平面设计师如何利用图片素材提升工作效率
  10. mysql表空间不足_MySQL Innodb表空间不足的处理方法 风好大