Android Ble蓝牙开发总结

原文出处

标题:Android Ble蓝牙开发总结

作者:Android_Gaomh

原文链接:Android Ble蓝牙开发总结_bluetoothgattcharacteristic property_notify-CSDN博客

前言

本文总结了ble的搜索,连接,读写操作。以及在开发过程中可能遇到的坑。

首先我们需要知道,什么是ble。

蓝牙发展至今经历了8个版本的更新。1.1、1.2、2.0、2.1、3.0、4.0、4.1、4.2。那么在1.x~3.0之间的我们称之为传统蓝牙,4.x开始的蓝牙我们称之为低功耗蓝牙也就是蓝牙ble。

蓝牙BLE相对于传统蓝牙的优点:最大化的待机时间、快速连接和低峰值的发送/接收功耗。应用区别:BLE低功耗蓝牙一般多用在蓝牙数据模块,拥有极低的运行和待机功耗,使用一粒纽扣电池可连续工作数年之久;BT经典蓝牙模块多用在蓝牙音频模块,音频需要大码流的数据传输更适合使用。

1、蓝牙BLE的发送和接受任务会以最快的速度完成,完成之后蓝牙BLE会暂停发射无线(但是还是会接受),等待下一次连接再激活;而传统蓝牙是持续保持连接。

2、广播信道(为保证网络不互相干扰而划分)仅有3个,而传统蓝牙是32个。

3、蓝牙低能耗技术“完成”一次连接(即扫描其它设备、建立链路、发送数据、认证和适当地结束)只需3ms。而标准蓝牙技术完成相同的连接周期需要数百毫秒。

4、蓝牙低能耗技术使用非常短的数据包,标准蓝牙技术使用的数据包长度较长。

ble的相关概念

Generic Attribute Profile (GATT)

通过BLE连接,读写属性类小数据的Profile通用规范。现在所有的BLE应用Profile都是基于GATT的。

Profile可以理解为一种规范,一个标准的通信协议,其存在于手机中,蓝牙组织规定了一些标准的profile:HID OVER GATT ,防丢器等,每个profile中包含了多个service。

Attribute Protocol (ATT)

GATT是基于ATT Protocol的。ATT针对BLE设备做了专门的优化,具体就是在传输过程中使用尽量少的数据。每个属性都有一个唯一的UUID,属性将以characteristics and services的形式传输。

Service

可以理解为一个服务,这里要区分的是BluetoothServer,一个是服务,一个是服务器端。在BLE从机中有多个服务,电量信息,系统服务信息等,每一个service中包含了多个characteristic特征值,每一个具体的characteristic特征值才是BLE通信的主题。 Characteristic的集合。例如一个service叫做“Heart Rate Monitor”,它可能包含多个Characteristics,其中可能包含一个叫做“heart rate measurement”的Characteristic。

Characteristic

Characteristic可以理解为一个数据类型,它包括一个value和0至多个对次value的描述(Descriptor)。通过操作Characteristic可以实现Ble的数据传输。

Descriptor

对Characteristic的描述,例如范围、计量单位等。

UUID(统一标识码)

service和characteristic均需要这个唯一的UUID进行标识。UUID可以双方自定义,例如客户端访问服务器端的写characteristic,那么客户端就需要有服务器端定义的写characteristic UUID

这三部分都用UUID作为唯一标识符。UUID为这种格式:0000ffe1-0000-1000-8000-00805f9b34fb。比如有3个Service,那么就有三个不同的UUID与Service对应。这些UUID都写在硬件里,我们通过BLE提供的API可以读取到,同时也可以自定义Application层的UUID。

他们关系可以总结如下:一个BLE终端可以包含多个Service, 一个Service可以包含多个Characteristic,一个Characteristic包含一个value和多个Descriptor,一个Descriptor包含一个Value。

Characteristic是比较重要的,是手机与BLE终端交换数据的关键,读取设置数据等操作都是操作Characteristic的相关属性。

代码实现

1.权限设置

使用蓝牙必须先获取到相应的权限,因为需要使用BLE,所以Manifest中需要加入以下权限及说明,在6.0上面需要使用BLE蓝牙,我们还需要加上LOCATION权限

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature
      android:name="android.hardware.bluetooth_le"
      android:required="true" />
2.检查设备蓝牙状态

判断当前设备是否支持蓝牙,并且是否开启蓝牙,如果支持且没有打开则需要开启蓝牙,要是不支持蓝牙,那就不用继续看了,直接说做不了就行了。

   mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
   mBluetoothAdapter = mBluetoothManager.getAdapter();
   if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
         Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
         startActivityForResult(intent, 0);
        }
3.搜索蓝牙设备
mBluetoothAdapter.startLeScan(scanCallback);
BluetoothAdapter.LeScanCallback scanCallback = new BluetoothAdapter.LeScanCallback() {
       @Override
       public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
           Log.e(TAG, "run: scanning..." + device.getName());
           if (device.getName() != null && !device.getName().equals("null") && !mDatas.contains(device)) {
            //展示当前搜索到的蓝牙设备
           }

       }
   };

4.获取蓝牙设备特征值
private void initServiceAndChara() {
      List<BluetoothGattService> bluetoothGattServices = mBluetoothGatt.getServices();
      for (BluetoothGattService bluetoothGattService : bluetoothGattServices) {
          List<BluetoothGattCharacteristic> characteristics = bluetoothGattService.getCharacteristics();
          for (BluetoothGattCharacteristic characteristic : characteristics) {
              int charaProp = characteristic.getProperties();
              if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
                  read_UUID_chara = characteristic.getUuid();
                  read_UUID_service = bluetoothGattService.getUuid();
                  Log.e(TAG, "read_chara=" + read_UUID_chara + "----read_service=" + read_UUID_service);
              }
              if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
                  write_UUID_chara = characteristic.getUuid();
                  write_UUID_service = bluetoothGattService.getUuid();
                  Log.e(TAG, "write_chara=" + write_UUID_chara + "----write_service=" + write_UUID_service);
              }
//                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) {
//                    write_UUID_chara = characteristic.getUuid();
//                    write_UUID_service = bluetoothGattService.getUuid();
//                    Log.e(TAG, "write_chara=" + write_UUID_chara + "----write_service=" + write_UUID_service);
//
//                }
              if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                  notify_UUID_chara = characteristic.getUuid();
                  notify_UUID_service = bluetoothGattService.getUuid();
                  Log.e(TAG, "notify_chara=" + notify_UUID_chara + "----notify_service=" + notify_UUID_service);
              }
              if ((charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
                  indicate_UUID_chara = characteristic.getUuid();
                  indicate_UUID_service = bluetoothGattService.getUuid();
                  Log.e(TAG, "indicate_chara=" + indicate_UUID_chara + "----indicate_service=" + indicate_UUID_service);

              }
          }
      }
  }

5.连接蓝牙设备
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             mBluetoothGatt = bluetoothDevice.connectGatt(MainActivity.this,
                            true, gattCallback, TRANSPORT_LE);
             } else {
              mBluetoothGatt = bluetoothDevice.connectGatt(MainActivity.this,
                            true, gattCallback);
                  }
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
      /**
       * 断开或连接 状态发生变化时调用
       * */
      @Override
      public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
          super.onConnectionStateChange(gatt, status, newState);
          Log.e(TAG, "onConnectionStateChange()");
          if (status == BluetoothGatt.GATT_SUCCESS) {
              //连接成功
              if (newState == BluetoothGatt.STATE_CONNECTED) {
                  Log.e(TAG, "连接成功");
                  isScaning = false;
                  //发现服务
                  gatt.discoverServices();
              }
          } else {
              //连接失败
              Log.e(TAG, "失败==" + status);
              mBluetoothGatt.close();
              isConnecting = false;
          }
      }

      /**
       * 发现设备(真正建立连接)
       * */
      @Override
      public void onServicesDiscovered(BluetoothGatt gatt, int status) {
          super.onServicesDiscovered(gatt, status);
          //直到这里才是真正建立了可通信的连接
          isConnecting = false;
          Log.e(TAG, "onServicesDiscovered()---建立连接");
          //获取初始化服务和特征值

          initServiceAndChara();
          //订阅通知
          mHandler.post(new Runnable() {
              @Override
              public void run() {
                  setCharacteristicNotification(mBluetoothGatt
                          .getService(indicate_UUID_service).getCharacteristic(indicate_UUID_chara), true);
              }
          });
      }

      /**
       * 读操作的回调
       * */
      @Override
      public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {
          super.onCharacteristicRead(gatt, characteristic, status);
          runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  Toast.makeText(MainActivity.this, "当前读取值:" + HexUtil.encodeHexStr(characteristic.getValue()), Toast.LENGTH_SHORT).show();
              }
          });

          Log.e(TAG, "onCharacteristicRead()" + HexUtil.encodeHexStr(characteristic.getValue()));
      }

      /**
       * 写操作的回调
       * */
      @Override
      public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
          super.onCharacteristicWrite(gatt, characteristic, status);

          final byte[] value = characteristic.getValue();
          Log.e(TAG, "onCharacteristicWrite()  status=" + status + ",value=" + bytesToHex(value));   
      }

      /**
       * 接收到硬件返回的数据
       * */
      @Override
      public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
          super.onCharacteristicChanged(gatt, characteristic);
          final byte[] data = characteristic.getValue();
          Log.e(TAG, "onCharacteristicChanged()" + bytesToHex(data));
      }
  };
6.蓝牙读写数据
  private void writeData() {
        BluetoothGattService service = mBluetoothGatt.getService(write_UUID_service);
        BluetoothGattCharacteristic charaWrite = service.getCharacteristic(write_UUID_chara);
        String content = etWriteContent.getText().toString();
        if (TextUtils.isEmpty(content)) {
            return;
        }
        byte[] body = content.getBytes();
        if (body.length > 20) {//数据大于个字节 分批次写入
            Log.e(TAG, "writeData: length=" + body.length);
            int num = 0;
            if (body.length % 20!= 0) {
                num = body.length / 20+ 1;
            } else {
                num = body.length / 20;
            }
            Log.e(TAG, "writeData: 需要" + num + "次");
            for (int i = 0; i < num; i++) {
                byte[] tempArr;
                if (i == num - 1) {
                    tempArr = new byte[body.length - i * 20];
                    System.arraycopy(body, i * 20, tempArr, 0, body.length - i * 20);
                } else {
                    tempArr = new byte[20];
                    System.arraycopy(body, i * 20, tempArr, 0,2017);
                }
            charaWrite.setValue(tempArr);
            mBluetoothGatt.writeCharacteristic(charaWrite);
            }
     
        } else {
            charaWrite.setValue(body);
            mBluetoothGatt.writeCharacteristic(charaWrite);
        }
    }
private void readData() {
        BluetoothGattCharacteristic characteristic = mBluetoothGatt.getService(read_UUID_service)
                .getCharacteristic(read_UUID_chara);
        mBluetoothGatt.readCharacteristic(characteristic);
    }

开发过程中可能遇到的坑

1.onCharacteristicChanged死活不回调
onServicesDiscovered回调后需要对相应的通道进行订阅。

setCharacteristicNotification(mBluetoothGatt
                          .getService(indicate_UUID_service).getCharacteristic(indicate_UUID_chara), true);
                          
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
                                            boolean enabled) {
      if (mBluetoothAdapter == null || mBluetoothGatt == null) {
          return;
      }
      boolean notification = mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
      //这里可以加入判断对指定的UUID值进行订阅
      List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
      for (BluetoothGattDescriptor descriptor : descriptors) {
          if (descriptor != null) {
              if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
                  boolean b = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
              } else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
                  boolean b = descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
              }
              mBluetoothGatt.writeDescriptor(descriptor);
          }
      }
  }

工具类

//合并byte[ ] 
  public static byte[] byteMerger(byte[] bt1, byte[] bt2) {
      byte[] bt3 = new byte[bt1.length + bt2.length];
      System.arraycopy(bt1, 0, bt3, 0, bt1.length);
      System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
      return bt3;
  }
    //byte[ ] -->16进制字符串
  public static String byte2hex(byte[] buffer) {
      String h = "";
      for (int i = 0; i < buffer.length; i++) {
          String temp = Integer.toHexString(buffer[i] & 0xFF);
          if (temp.length() == 1) {
              temp = "0" + temp;
          }
          h = h + temp;
      }
      return h;
  }

  // 16进制字符串 -->byte[ ]
  public static byte[] hexStringToByte(String hex) {
      int len = (hex.length() / 2);
      byte[] result = new byte[len];
      char[] achar = hex.toCharArray();
      for (int i = 0; i < len; i++) {
          int pos = i * 2;
          result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
      }
      return result;
  }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值