精华内容
下载资源
问答
  • android BLE蓝牙开发

    2021-05-28 08:57:21
    最近研究一下android上的ble应用开发。跟大家分享一下相关的内容。我的实现使用两台Android手机做ble相关的通信,一台机器作为服务端接收发过来的消息,另一台作为客户端发送消息。客户端基本流程如下:1.添加蓝牙...

    蓝牙BLE设备是目前比较热门的设备。由于BLE有低功耗等特点,被广泛应用到身边的电子产品上。如智能手表、手环、防丢器等各种产品上。最近研究一下android上的ble应用开发。跟大家分享一下相关的内容。

    我的实现使用两台Android手机做ble相关的通信,一台机器作为服务端接收发过来的消息,另一台作为客户端发送消息。

    客户端基本流程如下:

    92180296_1.png

    1.添加蓝牙相关权限

    2.BLE设备扫描使用BluetoothAdapter.startLeScan来扫描发现设备,这个方法需要参数BluetoothAdapter.LeScanCallback,所以还需要实现此回调方法,来获取扫描结果。

    注意:BLE扫描耗电量比较大,尽可能缩短扫描时间。发现用户所需连接的设备后立即停止扫描

    public classDeviceScanActivity extendsListActivity {

    privateBluetoothAdapter mBluetoothAdapter;private booleanmScanning;privateHandler mHandler;// 10秒后停止寻找.private static final longSCAN_PERIOD= 10000;private voidscanLeDevice(final booleanenable) {

    if(enable) {

    //经过预定扫描期后停止扫描mHandler.postDelayed(newRunnable() {

    @Overridepublic voidrun() {

    mScanning= false;mBluetoothAdapter.stopLeScan(mLeScanCallback);}

    },SCAN_PERIOD);mScanning= true;mBluetoothAdapter.startLeScan(mLeScanCallback);} else{

    mScanning= false;mBluetoothAdapter.stopLeScan(mLeScanCallback);}

    }

    }

    BLE扫描结果的接口,下面是BluetoothAdapter.LeScanCallback的实现。可以获取到扫描到设备的蓝牙名称和蓝牙地址等。

    privateBluetoothAdapter.LeScanCallback mLeScanCallback=

    newBluetoothAdapter.LeScanCallback() {

    @Overridepublic voidonLeScan(finalBluetoothDevice device, intrssi, byte[] scanRecord) {

    runOnUiThread(newRunnable() {

    @Overridepublic voidrun() {

    device.getName();//获得扫描到设备名称device.getAddress();//获取设备蓝牙地址}

    });}

    };

    3.连接

    先获取BluetoothAdapterd,再获取BluetoothDevice。根据扫描的蓝牙设备地址获取BluetoothDevice对象。使用BluetoothDevice.connectGatt进行连接。返回BluetoothGatt实例。

    然后使用connectGatt( )方法进行链接。这个方法需要三个参数:一个Context对象,自动连接(boolean值,表示只要BLE设备可用是否自动连接到它),和BluetoothGattCallback调用。后面会详细介绍BluetoothGattCallback的实现。

    BluetoothManager mBluetoothManager= (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);BluetoothAdapter mBluetoothAdapter= mBluetoothManager.getAdapter();BluetoothDevice mDevice= mBluetoothAdapter.getRemoteDevice(address);

    4.发现BLE设备服务

    mBluetoothGatt.discoverServices();

    5.发送Characteristic

    发送消息通过读写Characteristic来完成,app完成与GATT服务端连接和发现services后,就可以读写 writeCharacteristic,每个服务和characteristic都有一个UUID来唯一确定,所有想要开发BLE必须知道你想要用哪个服务的那个characteristic也就是要知道对应的UUID。

    private voidwriteCharacteristic(String writeValue) {

    BluetoothGattCharacteristic characteristic =

    getCharacteristic(CHARACTERISTIC_UUID);if(characteristic == null) return;characteristic.setValue(writeValue);mBluetoothGatt.writeCharacteristic(characteristic);}

    private voidreadCharacteristic() {

    BluetoothGattCharacteristic characteristic =

    getCharacteristic(CHARACTERISTIC_UUID);if(characteristic != null) mBluetoothGatt.readCharacteristic(characteristic);}

    6.BluetoothGattCallback实现

    根据BluetoothGattCallback判断当前状态。获取是否连接成功、是否断开连接、读写Characteristic是否成功等。

    private finalBluetoothGattCallback mGattCallbacks= newBluetoothGattCallback() {

    @Override//获取连接状态方法,BLE设备连接上或断开时,会调用到此方public voidonConnectionStateChange(BluetoothGatt gatt, intstatus, intnewState) {

    if(DEBUG) Log.d(TAG,"onConnectionStateChange");if(status == BluetoothGatt.GATT_SUCCESS) {

    if(newState == BluetoothProfile.STATE_CONNECTED) {

    showMessage("Bluetooth LE connected");}

    else if(status == BluetoothProfile.STATE_DISCONNECTED) {

    showMessage("Bluetooth LE disconnected");}

    }

    }

    //成功发现设备的services时,调用此方法@Overridepublic voidonServicesDiscovered(BluetoothGatt gatt, intstatus) {

    if((status == BluetoothGatt.GATT_SUCCESS) &&

    (mBluetoothGatt.getService(SERVICE_UUID) != null)) {

    showMessage("Discover service Successful !!!");}

    }

    //读写characteristic时会调用到以下方法@Overridepublic voidonCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, intstatus) {

    if((status == BluetoothGatt.GATT_SUCCESS) &&

    (characteristic.getUuid().equals(CHARACTERISTIC_UUID))) {

    showMessage(characteristic.getStringValue(0));}

    }

    @Overridepublic voidonCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, intstatus) {

    BluetoothGattCharacteristic mCharacteristic =

    getCharacteristic(CHARACTERISTIC_UUID);if((status == BluetoothGatt.GATT_SUCCESS) &&

    (characteristic.getStringValue(0).equals(mCharacteristic.getStringValue(0)))) {

    showMessage("CharacteristicWrite Successful !!!");}

    }

    };

    7.断开链接

    mBluetoothGatt.disconnect();

    以上是客户端(进行消息发送)的实现,下面介绍服务端实现方法。

    服务端实现比较简单,首先创建BluetoothManager,使用openGattServer实例mGattServer。openGattServer需要一个BluetoothGattServerCallback回调。创建BluetoothGattService,再把BluetoothGattService添加到BluetoothGattServer中。

    BluetoothManager mBluetoothManager= (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);BluetoothGattServer mGattServer= mBluetoothManager.openGattServer(this,mCallbacks);BluetoothGattService mService= createService();mGattServer.addService(mService);

    BluetoothGattServerCallback回调的实现,在这个回调里面可以获取到服务端发送过来的消息,连接状态等

    private finalBluetoothGattServerCallback mCallbacks= newBluetoothGattServerCallback() {

    @Override//获取连接状态方法,BLE设备连接上或断开时,会调用到此方public voidonConnectionStateChange(BluetoothDevice device, intstatus, intnewState) {

    if(DEBUG) Log.d(TAG,"onConnectionStateChange: newState="+ newState);if(status == BluetoothGatt.GATT_SUCCESS) {

    if(newState == BluetoothProfile.STATE_CONNECTED) {

    mDevice= device;String devicename = device.getName();String address = device.getAddress();notifyConnected(devicename);beginNotification();} else if(status == BluetoothProfile.STATE_DISCONNECTED) {

    stopNotification();notifyDisconnected();mDevice= null;}

    }

    }

    //service添加成功会调用此方@Overridepublic voidonServiceAdded(intstatus,BluetoothGattService service) {

    if(DEBUG) Log.d(TAG,"onServiceAdded()");if(status == BluetoothGatt.GATT_SUCCESS) notifyServiceAdded();}

    //读写Characteristic,在此获得客户端发来的消息@Overridepublic voidonCharacteristicWriteRequest(BluetoothDevice device, intrequestId,BluetoothGattCharacteristic characteristic,booleanpreparedWrite, booleanresponseNeeded,intoffset, byte[] value) {

    if(DEBUG) Log.d(TAG,"onCharacteristicWriteRequest: preparedWrite="+ preparedWrite);try{

    mCharacteristicString = newString(value);//客户端发来的消息}catch(Exception e){

    }

    notifyCharacteristicWriteRequest(mCharacteristicString);}

    }

    @Overridepublic voidonCharacteristicReadRequest(BluetoothDevice device, intrequestId,intoffset,BluetoothGattCharacteristic characteristic) {

    if(DEBUG) Log.d(TAG,"onCharacteristicReadRequest()");notifyCharacteristicReadRequest();}

    };

    再发几张我这个测试应用的操作截图

    1.在server端点击Start Server,上面文本提示BLE SERVICE ADDED

    92180296_2.png 

    2.在Client端,输入服务端手机的蓝牙地址,然后点在onnect

    92180296_3.png 

    3.进行配对,两台手机分别点击配对

    92180296_4.png 

    4.客户端点击Discover service,Toast提示Discover

    service Successful。

    服务端显示Device Connect。

    92180296_5.png 

    5.客户端输入要发送的字符"123456",点击Write Characteristic。

    服务端接收到发送过来的字符,显示Device Write:123456

    92180296_6.png 

    6.客户端点击Disconnect断开连接

    服务端显示Device Disconnected

    92180296_7.png

    展开全文
  • Android BLE 蓝牙开发框架,使用回调方式处理,搜索、连接、notify、indicate、读、写等一系列蓝牙操作
  • Android Ble蓝牙开发总结 前言 本文总结了ble的搜索,连接,读写操作。以及在开发过程中可能遇到的坑。 首先我们需要知道,什么是ble。 蓝牙发展至今经历了8个版本的更新。1.1、1.2、2.0、2.1、3.0、4.0、4.1、4.2。...

    Android Ble蓝牙开发总结

    前言

    本文总结了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;
      }
    

    结论

    项目还在进行,以后继续填坑,结束!

    展开全文
  • Android BLE蓝牙开发知识总结

    千次阅读 2018-08-11 17:29:28
    Android BLE蓝牙开发知识总结 1.蓝牙介绍 1.1什么是蓝牙?   蓝牙( Bluetooth® ):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF...

    Android BLE蓝牙开发知识总结

    1.蓝牙介绍

    1.1什么是蓝牙?

       蓝牙( Bluetooth® ):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHzISM波段的UHF无线电波)。蓝牙技术最初由电信巨头爱立信公司1994年创制,当时是作为RS232数据线的替代方案。如今蓝牙由蓝牙技术联盟(Bluetooth Special Interest Group,简称SIG)管理。

    https://baike.baidu.com/item/%E8%93%9D%E7%89%99/102670?fr=aladdin

    1.2蓝牙版本介绍

      蓝牙发展至今经历了8个版本的更新。1.11.22.02.13.04.04.14.25.0蓝牙规范1.02.0,即基本速率(BR)和增强数据速率(EDR),蓝牙技术规范3.0,即高速(HS)技术规范,最大数据速率达到了24Mb/s  

    具体版本介绍可参考(https://blog.csdn.net/androidstarjack/article/details/60468468

    按版本来划分:蓝牙可分为经典蓝牙模块(v1.1/1.2/2.0/2.1/3.0),低功耗蓝牙模块(v4.0/4.1/4.2),以及蓝牙双模模块(支持蓝牙所有版本,兼容低功耗蓝牙及经典蓝牙)。

    经典蓝牙支持音频(HFP/HSP, A2DP)和数据(SPP, HID, OPP, PBAP等)两大类协议,在音箱,耳机,汽车电子及传统数传行业,由于苹果对经典蓝牙数据传输接口有限制(需要过MFI认证),加上功耗偏大,因此在目前移动互联应用中慢慢地被淘汰。因此低功耗蓝牙4.0顺势而出,由于可支持苹果4S以上及安卓4.3系统以上的数据传输,且功耗极低,目前正在被越来越多的移动互联设备所采用,但不支持音频协议及受数据传输速度限制,其应用也被限制在小数据传输行业。而蓝牙双模则是综合了两者的优缺点,既可以支持音频传输,同样可支持数据传输,并且兼容性也是两者之和(安卓可不受系统限制,同样支持苹果4S以后的数据传输),在对功耗要求不苛刻的情况下,是比较理想的选择。

    2.低功耗蓝牙

    2.1 BLE介绍

    蓝牙 4.0 支持单模和双模两种部署方式,其中单模即是我们说的 BLE低功耗蓝牙(Bluetooth Low Energy),而双模指的是 Classic Bluetooth + BLE

    2.2 经典蓝牙(Classic Bluetooth)与低功耗蓝牙(BLE)的区别

    1)经典蓝牙可以用与数据量比较大的传输,如语音,音乐,较高数据量传输等。

    2BLE 特点就如其名,功耗更低的同时,对数据包做出了限制。所以适用于实时性要求比较高,但是数据速率比较低的产品,如鼠标,键盘,传感设备的数据发送等。

     

    Android 4.3API Level 18)开始引入Bluetooth Low EnergyBLE,低功耗蓝牙)的核心功能并提供了相应的 API 应用程序通过这些 API 扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作。(https://www.jianshu.com/p/3a372af38103

    Android BLE 使用的蓝牙协议是 GATT 协议, Android 开发中我们需要了解的一些 Bluetooth Low Energy 的专业术语。

    https://upload-images.jianshu.io/upload_images/4985212-4eb4addccb3fe853.ashx

    Figure 1蓝牙协议图

     

    Service

    一个低功耗蓝牙设备可以定义许多 Service, Service 可以理解为一个功能的集合。设备中每一个不同的 Service 都有一个 128 bit 的 UUID 作为这个 Service 的独立标志。蓝牙核心规范制定了两种不同的UUID,一种是基本的UUID,一种是代替基本UUID的16位UUID。所有的蓝牙技术联盟定义UUID共用了一个基本的UUID:
    0x0000xxxx-0000-1000-8000-00805F9B34FB

    为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的‘x’部分。例如,心率测量特性使用0X2A37作为它的16位UUID,因此它完整的128位UUID为: 0x00002A37-0000-1000-8000-00805F9B34FB。    每一个service 和 characteristic在蓝牙的官网里面都有一个assgined Number (替代基本UUID的‘x’部分)。参考如何获得每个Service Characteristicuuid

    手机的BLE默认有2个服务 
        (1)Service 通用属性规范 00001801-0000-1000-8000-00805f9b34fb (null) 
                 a)Characteristic 服务改变 00002a05-0000-1000-8000-00805f9b34fb 
        (2)Service 通用接入规范 00001800-0000-1000-8000-00805f9b34fb 
                 a)Characteristic 设备名称 00002a00-0000-1000-8000-00805f9b34fb 
                 b)Characteristic 设备外观 00002a01-0000-1000-8000-00805f9b34fb (00 00) 
     

    Characteristic

    在 Service 下面,又包括了许多的独立数据项,我们把这些独立的数据项称作 Characteristic。同样的,每一个 Characteristic 也有一个唯一的 UUID 作为标识符。在 Android 开发中,建立蓝牙连接后,我们说的通过蓝牙发送数据给外围设备就是往这些 Characteristic 中的 Value 字段写入数据;外围设备发送数据给手机就是监听这些 Charateristic 中的 Value 字段有没有变化,如果发生了变化,手机的 BLE API 就会收到一个监听的回调。

     

    2.3 Android BLE API 简介

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

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

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

    BluetoothGattService
        这个类通过 BluetoothGatt#getService 获得,如果当前服务不可见那么将返回一个 null。这个类对应上面说过的 Service。我们可以通过这个类的 getCharacteristic(UUID uuid) 进一步获取 Characteristic 实现蓝牙数据的双向传输。

    BluetoothGattCharacteristic
        这个类对应上面提到的 Characteristic。通过这个类定义需要往外围设备写入的数据和读取外围设备发送过来的数据。

    2.4 Android 蓝牙开发示例

    第一步:声明所需要的权限

    <uses-permission android:name="android.permission.BLUETOOTH" />使用蓝牙所需要的权限
    
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />使用扫描和设置蓝牙的权限(申明这一个权限必须申明上面一个权限)
    

    在Android5.0之前,是默认申请GPS硬件功能的。而在Android 5.0 之后,需要在manifest 中申明GPS硬件模块功能的使用。

    <uses-feature android:name="android.hardware.location.gps" />

    在 Android 6.0 及以上,还需要打开位置权限。如果应用没有位置权限,蓝牙扫描功能不能使用(其它蓝牙操作例如连接蓝牙设备和写入数据不受影响)(https://blog.csdn.net/kjunchen/article/details/52769915)。

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

    第二步:连接蓝牙前的初始化工作

    在建立蓝牙连接之前,需要确认设备支持 BLE。如果支持,再确认蓝牙是否开启。如果蓝牙没有开启,可以使用 BLuetoothAdapter 类来开启蓝牙。

    获取 BluetoothAdapter

     private BluetoothAdapter mBluetoothAdapter;
            
        // Initializes Bluetooth adapter.
        final BluetoothManager bluetoothManager =
            (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();
    

    如果检测到蓝牙没有开启,尝试开启蓝牙。开启蓝牙方式有两种:

    第一种直接简单暴力不给用户进行提示:

    if (!mBluetoothAdapter.isEnabled()) {
                mBluetoothAdapter.enable();
    }

    第二种优雅的践行开启并且有弹框进行提示,隐式启动Intent:

    if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }

    第三步:扫描蓝牙设备

    外围设备开启蓝牙后,会广播出许多的关于该设备的数据信息,例如 mac 地址,uuid 等等。通过这些数据我们可以筛选出需要的设备。

    在 BluetoothAdapter 中,我们可以看到有两个扫描蓝牙的方法。第一个方法可以指定只扫描含有特定 UUID Service 的蓝牙设备,第二个方法则是扫描全部蓝牙设备。

    boolean startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)
    boolean startLeScan(BluetoothAdapter.LeScanCallback callback)

    开启蓝牙扫描

    final BluetoothAdapter.LeScanCallback callback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
            bluetoothDeviceArrayList.add(device); 
            Log.d(TAG, "run: scanning...");
        }
    };
    mBluetoothAdapter.startLeScan(callback);

    在 LeScanCallback 回调的方法中,第一个参数是代表蓝牙设备的类,可以通过这个类建立蓝牙连接获取关于这一个设备的一系列详细的参数,例如名字,MAC 地址等等;第二个参数是蓝牙的信号强弱指标,通过蓝牙的信号指标,我们可以大概计算出蓝牙设备离手机的距离。计算公式为:d = 10^((abs(RSSI) - A) / (10 * n))(A:发射端和接收端相隔1米时的信号强度, n 环境衰减因子,An的值,需要根据实际环境进行检测得出https://blog.csdn.net/it_beecoder/article/details/61429473);第三个参数是蓝牙广播出来的广告数据,包含 广播数据 和 扫描响应数据 (如果有的话),所以长度一般就是 62 字节,BLE4.0规定,如果广播包和扫描应答包不足字节,则以0补齐。BLE广播数据包分析参考https://www.cnblogs.com/smart-mutouren/p/5882038.html,http://makaidong.com/roshen_android/1/39683_9246339_2.htm。

    当执行上面的代码之后,一旦发现蓝牙设备,LeScanCallback 就会被回调,直到 stopLeScan 被调用。出现在回调中的设备会重复出现,所以如果我们需要通过 BluetoothDevice 获取外围设备的地址手动过滤掉已经发现的外围设备。

     

    停止蓝牙扫描

    void    stopLeScan(BluetoothAdapter.LeScanCallback callback)

    通过调用 BluetoothAdapter#stopLeScan 可以停止正在进行的蓝牙扫描。这里需要注意的是,传入的回调必须是开启蓝牙扫描时传入的回调,否则蓝牙扫描不会停止。

    由于蓝牙扫描的操作比较消耗手机的能量。所以我们不能一直开着蓝牙,必须设置一段时间之后关闭蓝牙扫描。示例代码如下:

    private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            // 预先定义停止蓝牙扫描的时间(因为蓝牙扫描需要消耗较多的电量)
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);
            mScanning = true;
    
            // 定义一个回调接口供扫描结束处理
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
    }

    注:(https://www.cnblogs.com/Free-Thinker/p/6419433.html

    1.android 4.3.1(Build.VERSION_CODES.JELLY_BEAN_MR2)增加的startLeScan(callback)方法,官方在5.0之后不建议使用,实测此方法,4.3至目前6.0版本还是很稳定的,毫秒级无限返回蓝牙数据,很稳定,就是很耗电;

    2.android5.0(Build.VERSION_CODES.LOLLIPOP)谷歌建议使用BluetoothAdapter.getBluetoothLeScanner().startScan(mScanCallback),实测效果不尽人意,这个接口很不稳定,前几十秒扫描非常稳定,快速返回很多个BLE装置后(大概持续1-2分钟),然后蓝牙自动静默,回调方法无任何数据返回,我们几百台BLE设备实测。

    ​​​​​​​第四步:连接蓝牙设备

    连接蓝牙设备可以通过 BluetoothDevice#ConnectGatt 方法连接,也可以通过 BluetoothGatt#connect 方法进行重新连接。以下分别是两个方法的官方说明:

    BluetoothDevice#connectGatt
    BluetoothGatt   connect(Context context, boolean autoConnect, BluetoothGattCallback callback)

       第二个参数表示是否需要自动连接。如果设置为 true, 表示如果设备断开了,会不断的尝试自动连接。设置为 false 表示只进行一次连接尝试。第三个参数是连接后进行的一系列操作的回调,例如连接和断开连接的回调,发现服务的回调,成功写入数据,成功读取数据的回调等等。

    BluetoothGatt#connect
    boolean connect()

    用这一个方法相当与调用 BluetoothDevice#connectGatt 且第二个参数 autoConnect 设置为 true。

    当调用蓝牙的连接方法之后,蓝牙会异步执行蓝牙连接的操作,如果连接成功会回调 BluetoothGattCalback#onConnectionStateChange 方法。这个方法运行的线程是一个 Binder 线程,所以不建议直接在这个线程处理耗时的任务,因为这可能导致蓝牙相关的线程被阻塞。

    void   onConnectionStateChange(BluetoothGatt gatt, int status, int newState)

    这一个方法有三个参数,第一个就蓝牙设备的 Gatt 服务连接类。第二个参数代表是否成功执行了连接操作,如果为 BluetoothGatt.GATT_SUCCESS 表示成功执行连接操作,第三个参数才有效,否则说明这次连接尝试不成功。有时候,我们会遇到 status == 133 的情况,根据网上大部分人的说法,这是因为 Android 最多支持连接 6 到 7 个左右的蓝牙设备,如果超出了这个数量就无法再连接了。所以当我们断开蓝牙设备的连接时,还必须调用 BluetoothGatt#close 方法释放连接资源。否则,在多次尝试连接蓝牙设备之后很快就会超出这一个限制,导致出现这一个错误再也无法连接蓝牙设备。第三个参数代表当前设备的连接状态,如果 newState == BluetoothProfile.STATE_CONNECTED 说明设备已经连接,可以进行下一步的操作了(发现蓝牙服务,也就是 Service)。当蓝牙设备断开连接时,这一个方法也会被回调,其中的 newState == BluetoothProfile.STATE_DISCONNECTED。

    ​​​​​​​第五步:发现服务

    在成功连接到蓝牙设备之后才能进行这一个步骤,也就是说在 BluetoothGattCallback#onConnectionStateChange 方法被成功回调且表示成功连接之后调用 BluetoothGatt#discoverService 这一个方法。当这一个方法被调用之后,系统会异步执行发现服务的过程,直到 BluetoothGattCallback#onServicesDiscovered 被系统回调之后,手机设备和蓝牙设备才算是真正建立了可通信的连接。

    到这一步,我们已经成功和蓝牙设备建立了可通信的连接,接下来就可以执行相应的蓝牙通信操作了,例如写入数据,读取蓝牙设备的数据等等。

              读取数据

    当我们发现服务之后就可以通过 BluetoothGatt#getService 获取 BluetoothGattService,接着通过 BluetoothGattService#getCharactristic 获取 BluetoothGattCharactristic。通过 BluetoothGattCharactristic#readCharacteristic 方法可以通知系统去读取特定的数据。如果系统读取到了蓝牙设备发送过来的数据就会调用 BluetoothGattCallback#onCharacteristicRead 方法。通过 BluetoothGattCharacteristic#getValue 可以读取到蓝牙设备的数据。以下是代码示例:

    @Override
    public void onCharacteristicRead(final BluetoothGatt gatt,
                                        final BluetoothGattCharacteristic characteristic,final int status) {
    
        Log.d(TAG, "callback characteristic read status " + status
                + " in thread " + Thread.currentThread());
        if (status == BluetoothGatt.GATT_SUCCESS) {
            Log.d(TAG, "read value: " + characteristic.getValue());
        }
    }
    
    // 读取数据
    BluetoothGattService service = gatt.getService(SERVICE_UUID);
    BluetoothGattCharacteristic characteristic = gatt.getCharacteristic(CHARACTER_UUID);
    gatt.readCharacteristic();

    ​​​​​​​       写入数据

    和读取数据一样,在执行写入数据前需要获取到 BluetoothGattCharactristic。接着执行一下步骤:

    1. 调用 BluetoothGattCharactristic#setValue 传入需要写入的数据(蓝牙最多单次1支持 20 个字节数据的传输,如果需要传输的数据大于这一个字节则需要分包传输)。
    2. 调用 BluetoothGattCharactristic#writeCharacteristic 方法通知系统异步往设备写入数据。
    3. 系统回调 BluetoothGattCallback#onCharacteristicWrite 方法通知数据已经完成写入。此时,我们需要执行 BluetoothGattCharactristic#getValue 方法检查一下写入的数据是否我们需要发送的数据,如果不是按照项目的需要判断是否需要重发。
      以下是示例代码:
    @Override
    public void onCharacteristicWrite(final BluetoothGatt gatt,
                                        final BluetoothGattCharacteristic characteristic,
                                        final int status) {
        Log.d(TAG, "callback characteristic write in thread " + Thread.currentThread());
        if(!characteristic.getValue().equal(sendValue)) {
            // 执行重发策略
            gatt.writeCharacteristic(characteristic);
        }
    }
    
    //往蓝牙数据通道的写入数据
    BluetoothGattService service = gattt.getService(SERVICE_UUID);
    BluetoothGattCharacteristic characteristic = gatt.getCharacteristic(CHARACTER_UUID);
    characteristic.setValue(sendValue);
    gatt.writeCharacteristic(characteristic);

    向蓝牙设备注册监听实现实时读取蓝牙设备的数据

    BLE app通常需要获取设备中characteristic 变化的通知。下面的代码演示了怎么为一个Characteristic 设置一个监听。

    mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
    
    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
            UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);

    值得注意的是,除了通过 BluetoothGatt#setCharacteristicNotification 开启 Android 端接收通知的开关,还需要往 Characteristic 的 Descriptor 属性写入开启通知的数据开关使得当硬件的数据改变时,主动往手机发送数据。​​​​​​​

    最后一步:断开连接

    当我们连接蓝牙设备完成一系列的蓝牙操作之后就可以断开蓝牙设备的连接了。通过 BluetoothGatt#disconnect 可以断开正在连接的蓝牙设备。当这一个方法被调用之后,系统会异步回调 BluetoothGattCallback#onConnectionStateChange 方法。通过这个方法的 newState 参数可以判断是连接成功还是断开成功的回调。

    由于 Android 蓝牙连接设备的资源有限,当我们执行断开蓝牙操作之后必须执行 BluetoothGatt#close 方法释放资源。需要注意的是通过 BluetoothGatt#close 方法也可以执行断开蓝牙的操作,不过 BluetoothGattCallback#onConnectionStateChange 将不会收到任何回调。此时如果执行 BluetoothGatt#connect 方法会得到一个蓝牙 API 的空指针异常。所以,我们推荐的写法是当蓝牙成功连接之后,通过 BluetoothGatt#disconnect 断开蓝牙的连接,紧接着在 BluetoothGattCallback#onConnectionStateChange 执行 BluetoothGatt#close 方法释放资源。以下是代码示例:
     

        
    @Override
        public void onConnectionStateChange(final BluetoothGatt gatt, final int status,
                                        final int newState) {
            Log.d(TAG, "onConnectionStateChange: thread "
                    + Thread.currentThread() + " status " + newState);
    
            if (status != BluetoothGatt.GATT_SUCCESS) {
                String err = "Cannot connect device with error status: " + status;
          // 当尝试连接失败的时候调用 disconnect 方法是不会引起这个方法回调的,所以这里
                    //   直接回调就可以了。
                gatt.close();
                Log.e(TAG, err);
                return;
            }
    
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                gatt.discoverService();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                gatt.close();
            }
    }

    ​​​​​​​蓝牙操作的注意事项

    1、蓝牙的写入操作( 包括 Descriptor 的写入操作),读取操作必须序列化进行。 写入数据和读取数据是不能同时进行的, 如果调用了写入数据的方法,马上又调用写入数据或者读取数据的方法,第二次调用的方法会立即返回 false, 代表当前无法进行操作。 蓝牙读写操作返回 false,为什么多次读写只有一次回调?

    2、Android 连接外围设备的数量有限,当不需要连接蓝牙设备的时候,必须调用 BluetoothGatt#close 方法释放资源。

    3、蓝牙 API 连接蓝牙设备的超时时间大概在 20s 左右,具体时间看系统实现。有时候某些设备进行蓝牙连接的时间会很长,大概十多秒。如果自己手动设置了连接超时时间(例如通过 Handler#postDelay 设置了 5s 后没有进入 BluetoothGattCallback#onConnectionStateChange 就执行 BluetoothGatt#close 操作强制释放断开连接释放资源)在某些设备上可能会导致接下来几次的连接尝试都会在 BluetoothGattCallback#onConnectionStateChange 返回 state == 133。参考这篇 Android BLE 连接出现“BluetoothGatt status 133”的解决方法

    4、所有的蓝牙操作使用 Handler 固定在一条线程操作,这样能省去很多因为线程不同步导致的麻烦。

     

    3 参考资料

    https://baike.baidu.com/item/%E8%93%9D%E7%89%99/102670?fr=aladdin

    https://blog.csdn.net/androidstarjack/article/details/60468468

    https://baijiahao.baidu.com/s?id=1571454926736422&wfr=spider&for=pc

    https://www.zhihu.com/question/60824531/answer/181351944

    https://www.jianshu.com/p/3a372af38103

    https://blog.csdn.net/androidstarjack/article/details/60595241

    有关demo:https://github.com/androidstarjack/Bluetooth_4.3-masterhttps://github.com/Belolme/RxBLE

    展开全文
  • 根据官方提供示例略有更改
  • Android BLE蓝牙开发示例
  • 作者Jasonchenlijian,源码FastBle,Android BLE 蓝牙快速开发框架,使用回调方式处理:scan、connect、notify、indicate、write、read等一系列蓝牙操作。每一个characteristic会与一个callback形成一一对应的监听...
  • Android 4.3(API Level 18)开始引入Bluetooth Low Energy(BLE,低功耗蓝牙)的核心功能并提供了相应的 API, 应用程序通过这些 API 扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作...

    初识低功耗蓝牙
    Android 4.3(API Level 18)开始引入Bluetooth Low Energy(BLE,低功耗蓝牙)的核心功能并提供了相应的 API, 应用程序通过这些 API 扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作。

    Android BLE 使用的蓝牙协议是 GATT 协议,有关该协议的详细内容可以参见蓝牙官方文档。以下我引用一张官网的图来大概说明 Android 开发中我们需要了解的一些 Bluetooth Low Energy 的专业术语。
    蓝牙协议图
    Service
    一个低功耗蓝牙设备可以定义许多 Service, Service 可以理解为一个功能的集合。设备中每一个不同的 Service 都有一个 128 bit 的 UUID 作为这个 Service 的独立标志。蓝牙核心规范制定了两种不同的UUID,一种是基本的UUID,一种是代替基本UUID的16位UUID。所有的蓝牙技术联盟定义UUID共用了一个基本的UUID:
    0x0000xxxx-0000-1000-8000-00805F9B34FB
    为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的‘x’部分。例如,心率测量特性使用0X2A37作为它的16位UUID,因此它完整的128位UUID为:
    0x00002A37-0000-1000-8000-00805F9B34FB

    Characteristic
    在 Service 下面,又包括了许多的独立数据项,我们把这些独立的数据项称作 Characteristic。同样的,每一个 Characteristic 也有一个唯一的 UUID 作为标识符。在 Android 开发中,建立蓝牙连接后,我们说的通过蓝牙发送数据给外围设备就是往这些 Characteristic 中的 Value 字段写入数据;外围设备发送数据给手机就是监听这些 Charateristic 中的 Value 字段有没有变化,如果发生了变化,手机的 BLE API 就会收到一个监听的回调。

    更详细的内容可以参见
    GATT Profile 简介
    通用属性配置文件(GATT)及其服务,特性与属性介绍
    GATT specification
    GATT Services
    蓝牙【GATT】协议介绍

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

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

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

    BluetoothGattService
    这一个类通过 BluetoothGatt#getService 获得,如果当前服务不可见那么将返回一个 null。这一个类对应上面说过的 Service。我们可以通过这个类的 getCharacteristic(UUID uuid) 进一步获取 Characteristic 实现蓝牙数据的双向传输。

    BluetoothGattCharacteristic
    这个类对应上面提到的 Characteristic。通过这个类定义需要往外围设备写入的数据和读取外围设备发送过来的数据。

    Android 蓝牙开发示例
    第一步:声明所需要的权限
    使用蓝牙所需要的权限
    使用扫描和设置蓝牙的权限(申明这一个权限必须申明上面一个权限)
    在Android5.0之前,是默认申请GPS硬件功能的。而在Android 5.0 之后,需要在manifest 中申明GPS硬件模块功能的使用。

    <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
    <uses-feature android:name="android.hardware.location.gps" />
    

    在 Android 6.0 及以上,还需要打开位置权限。如果应用没有位置权限,蓝牙扫描功能不能使用(其它蓝牙操作例如连接蓝牙设备和写入数据不受影响)。

    第二步:连接蓝牙前的初始化工作 在建立蓝牙连接之前,需要确认设备支持 BLE。如果支持,再确认蓝牙是否开启。如果蓝牙没有开启,可以使用 BLuetoothAdapter 类来开启蓝牙。

    获取 BluetoothAdapter
    private BluetoothAdapter mBluetoothAdapter;

    // Initializes Bluetooth adapter.
    final BluetoothManager bluetoothManager =
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    mBluetoothAdapter = bluetoothManager.getAdapter();
    

    如果检测到蓝牙没有开启,尝试开启蓝牙
    // Ensures Bluetooth is available on the device and it is enabled. If not,
    // displays a dialog requesting user permission to enable Bluetooth.
    if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }
    第三步:扫描蓝牙设备
    外围设备开启蓝牙后,会广播出许多的关于该设备的数据信息,例如 mac 地址,uuid 等等。通过这些数据我们可以筛选出需要的设备。

    在 BluetoothAdapter 中,我们可以看到有两个扫描蓝牙的方法。第一个方法可以指定只扫描含有特定 UUID Service 的蓝牙设备,第二个方法则是扫描全部蓝牙设备。

    boolean startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)
    boolean startLeScan(BluetoothAdapter.LeScanCallback callback)
    开启蓝牙扫描

    final BluetoothAdapter.LeScanCallback callback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
    bluetoothDeviceArrayList.add(device);
    Log.d(TAG, “run: scanning…”);
    }
    };

    mBluetoothAdapter.startLeScan(callback);
    在 LeScanCallback 回调的方法中,第一个参数是代表蓝牙设备的类,可以通过这个类建立蓝牙连接获取关于这一个设备的一系列详细的参数,例如名字,MAC 地址等等;第二个参数是蓝牙的信号强弱指标,通过蓝牙的信号指标,我们可以大概计算出蓝牙设备离手机的距离。计算公式为:d = 10^((abs(RSSI) - A) / (10 * n));第三个参数是蓝牙广播出来的广告数据。
    当执行上面的代码之后,一旦发现蓝牙设备,LeScanCallback 就会被回调,直到 stopLeScan 被调用。出现在回调中的设备会重复出现,所以如果我们需要通过 BluetoothDevice 获取外围设备的地址手动过滤掉已经发现的外围设备。

    停止蓝牙扫描

    void stopLeScan(BluetoothAdapter.LeScanCallback callback)
    通过调用 BluetoothAdapter#stopLeScan 可以停止正在进行的蓝牙扫描。这里需要注意的是,传入的回调必须是开启蓝牙扫描时传入的回调,否则蓝牙扫描不会停止。

    由于蓝牙扫描的操作比较消耗手机的能量。所以我们不能一直开着蓝牙,必须设置一段时间之后关闭蓝牙扫描。示例代码如下:

    private void scanLeDevice(final boolean enable) {
    if (enable) {
    // Stops scanning after a pre-defined scan period.
    // 预先定义停止蓝牙扫描的时间(因为蓝牙扫描需要消耗较多的电量)
    mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
    mScanning = false;
    mBluetoothAdapter.stopLeScan(mLeScanCallback);
    }
    }, SCAN_PERIOD);
    mScanning = true;

        // 定义一个回调接口供扫描结束处理
        mBluetoothAdapter.startLeScan(mLeScanCallback);
    } else {
        mScanning = false;
        mBluetoothAdapter.stopLeScan(mLeScanCallback);
    }
    

    }
    第四步:连接蓝牙设备
    连接蓝牙设备可以通过 BluetoothDevice#ConnectGatt 方法连接,也可以通过 BluetoothGatt#connect 方法进行重新连接。以下分别是两个方法的官方说明:

    BluetoothDevice#connectGatt
    BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)
    第二个参数表示是否需要自动连接。如果设置为 true, 表示如果设备断开了,会不断的尝试自动连接。设置为 false 表示只进行一次连接尝试。
    第三个参数是连接后进行的一系列操作的回调,例如连接和断开连接的回调,发现服务的回调,成功写入数据,成功读取数据的回调等等。

    BluetoothGatt#connect
    boolean connect()
    调用这一个方法相当与调用 BluetoothDevice#connectGatt 且第二个参数 autoConnect 设置为 true。

    当调用蓝牙的连接方法之后,蓝牙会异步执行蓝牙连接的操作,如果连接成功会回调 BluetoothGattCalbackl#onConnectionStateChange 方法。这个方法运行的线程是一个 Binder 线程,所以不建议直接在这个线程处理耗时的任务,因为这可能导致蓝牙相关的线程被阻塞。

    void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
    这一个方法有三个参数,第一个就蓝牙设备的 Gatt 服务连接类。
    第二个参数代表是否成功执行了连接操作,如果为 BluetoothGatt.GATT_SUCCESS 表示成功执行连接操作,第三个参数才有效,否则说明这次连接尝试不成功。有时候,我们会遇到 status == 133 的情况,根据网上大部分人的说法,这是因为 Android 最多支持连接 6 到 7 个左右的蓝牙设备,如果超出了这个数量就无法再连接了。所以当我们断开蓝牙设备的连接时,还必须调用 BluetoothGatt#close 方法释放连接资源。否则,在多次尝试连接蓝牙设备之后很快就会超出这一个限制,导致出现这一个错误再也无法连接蓝牙设备。
    第三个参数代表当前设备的连接状态,如果 newState == BluetoothProfile.STATE_CONNECTED 说明设备已经连接,可以进行下一步的操作了(发现蓝牙服务,也就是 Service)。当蓝牙设备断开连接时,这一个方法也会被回调,其中的 newState == BluetoothProfile.STATE_DISCONNECTED。

    第五步:发现服务
    在成功连接到蓝牙设备之后才能进行这一个步骤,也就是说在 BluetoothGattCalbackl#onConnectionStateChang 方法被成功回调且表示成功连接之后调用 BluetoothGatt#discoverService 这一个方法。当这一个方法被调用之后,系统会异步执行发现服务的过程,直到 BluetoothGattCallback#onServicesDiscovered 被系统回调之后,手机设备和蓝牙设备才算是真正建立了可通信的连接。

    到这一步,我们已经成功和蓝牙设备建立了可通信的连接,接下来就可以执行相应的蓝牙通信操作了,例如写入数据,读取蓝牙设备的数据等等。

    读取数据
    当我们发现服务之后就可以通过 BluetoothGatt#getService 获取 BluetoothGattService,接着通过 BluetoothGattService#getCharactristic 获取 BluetoothGattCharactristic。
    通过 BluetoothGattCharactristic#readCharacteristic 方法可以通知系统去读取特定的数据。如果系统读取到了蓝牙设备发送过来的数据就会调用 BluetoothGattCallback#onCharacteristicRead 方法。通过 BluetoothGattCharacteristic#getValue 可以读取到蓝牙设备的数据。以下是代码示例:

    @Override
    public void onCharacteristicRead(final BluetoothGatt gatt,
    final BluetoothGattCharacteristic characteristic,
    final int status) {

    Log.d(TAG, "callback characteristic read status " + status
            + " in thread " + Thread.currentThread());
    if (status == BluetoothGatt.GATT_SUCCESS) {
        Log.d(TAG, "read value: " + characteristic.getValue());
    }
    

    }

    // 读取数据
    BluetoothGattService service = gattt.getService(SERVICE_UUID);
    BluetoothGattCharacteristic characteristic = gatt.getCharacteristic(CHARACTER_UUID);
    gatt.readCharacteristic();
    写入数据
    和读取数据一样,在执行写入数据前需要获取到 BluetoothGattCharactristic。接着执行一下步骤:

    调用 BluetoothGattCharactristic#setValue 传入需要写入的数据(蓝牙最多单次1支持 20 个字节数据的传输,如果需要传输的数据大于这一个字节则需要分包传输)。
    调用 BluetoothGattCharactristic#writeCharacteristic 方法通知系统异步往设备写入数据。
    系统回调 BluetoothGattCallback#onCharacteristicWrite 方法通知数据已经完成写入。此时,我们需要执行 BluetoothGattCharactristic#getValue 方法检查一下写入的数据是否我们需要发送的数据,如果不是按照项目的需要判断是否需要重发。
    以下是示例代码:
    @Override
    public void onCharacteristicWrite(final BluetoothGatt gatt,
    final BluetoothGattCharacteristic characteristic,
    final int status) {
    Log.d(TAG, "callback characteristic write in thread " + Thread.currentThread());
    if(!characteristic.getValue().equal(sendValue)) {
    // 执行重发策略
    gatt.writeCharacteristic(characteristic);
    }
    }

    //往蓝牙数据通道的写入数据
    BluetoothGattService service = gattt.getService(SERVICE_UUID);
    BluetoothGattCharacteristic characteristic = gatt.getCharacteristic(CHARACTER_UUID);
    characteristic.setValue(sendValue);
    gatt.writeCharacteristic(characteristic);
    向蓝牙设备注册监听实现实时读取蓝牙设备的数据
    BLE app通常需要获取设备中characteristic 变化的通知。下面的代码演示了怎么为一个Characteristic 设置一个监听。

    mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
    UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);
    值得注意的是,除了通过 BluetoothGatt#setCharacteristicNotification 开启 Android 端接收通知的开关,还需要往 Characteristic 的 Descriptor 属性写入开启通知的数据开关使得当硬件的数据改变时,主动往手机发送数据。

    最后一步:断开连接
    当我们连接蓝牙设备完成一系列的蓝牙操作之后就可以断开蓝牙设备的连接了。通过 BluetoothGatt#disconnect 可以断开正在连接的蓝牙设备。当这一个方法被调用之后,系统会异步回调 BluetoothGattCallback#onConnectionStateChange 方法。通过这个方法的 newState 参数可以判断是连接成功还是断开成功的回调。
    由于 Android 蓝牙连接设备的资源有限,当我们执行断开蓝牙操作之后必须执行 BluetoothGatt#close 方法释放资源。需要注意的是通过 BluetoothGatt#close 方法也可以执行断开蓝牙的操作,不过 BluetoothGattCallback#onConnectionStateChange 将不会收到任何回调。此时如果执行 BluetoothGatt#connect 方法会得到一个蓝牙 API 的空指针异常。所以,我们推荐的写法是当蓝牙成功连接之后,通过 BluetoothGatt#disconnect 断开蓝牙的连接,紧接着在 BluetoothGattCallback#onConnectionStateChange 执行 BluetoothGatt#close 方法释放资源。
    以下是代码示例:

    @Override
    public void onConnectionStateChange(final BluetoothGatt gatt, final int status,
                                    final int newState) {
        Log.d(TAG, "onConnectionStateChange: thread "
                + Thread.currentThread() + " status " + newState);
    
        if (status != BluetoothGatt.GATT_SUCCESS) {
            String err = "Cannot connect device with error status: " + status;
      // 当尝试连接失败的时候调用 disconnect 方法是不会引起这个方法回调的,所以这里
                //   直接回调就可以了。
            gatt.close();
            Log.e(TAG, err);
            return;
        }
    
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            gatt.discoverService();
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            gatt.close();
        }
    }
    

    蓝牙操作的注意事项
    蓝牙的写入操作( 包括 Descriptor 的写入操作), 读取操作必须序列化进行. 写入数据和读取数据是不能同时进行的, 如果调用了写入数据的方法, 马上调用又调用写入数据或者读取数据的方法,第二次调用的方法会立即返回 false, 代表当前无法进行操作. 详情可以参考 蓝牙读写操作返回 false,为什么多次读写只有一次回调?
    Android 连接外围设备的数量有限,当不需要连接蓝牙设备的时候,必须调用 BluetoothGatt#close 方法释放资源。详细的参考可以看这里 Android BLE 蓝牙开发的各种坑
    蓝牙 API 连接蓝牙设备的超时时间大概在 20s 左右,具体时间看系统实现。有时候某些设备进行蓝牙连接的时间会很长,大概十多秒。如果自己手动设置了连接超时时间(例如通过 Handler#postDelay 设置了 5s 后没有进入 BluetoothGattCallback#onConnectionStateChange 就执行 BluetoothGatt#close 操作强制释放断开连接释放资源)在某些设备上可能会导致接下来几次的连接尝试都会在 BluetoothGattCallback#onConnectionStateChange 返回 state == 133。另外可以参考这篇吐槽 Android 中 BLE 连接出现“BluetoothGatt status 133”的解决方法
    所有的蓝牙操作使用 Handler 固定在一条线程操作,这样能省去很多因为线程不同步导致的麻烦
    最后,附上我写的一个使用 RxJava 封装蓝牙操作库,大家可以参考我的类库源码根据自己项目的需求二次开发。https://github.com/Belolme/RxBLE

    参考:https://www.jianshu.com/p/3a372af38103

    展开全文
  • Android 4.3(API Level 18)开始引入Bluetooth Low Energy(BLE,低功耗蓝牙)的核心功能并提供了相应的 API, 应用程序通过这些 API 扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作...
  • Android ble蓝牙开发介绍以及遇到的坑

    千次阅读 2017-08-19 17:18:22
    Android ble蓝牙开发 BLE介绍 安卓4.3(API 18)为BLE的核心功能提供平台支持和API,App可以利用它来发现设备、查询服务和读写特性。相比传统的蓝牙,BLE更显著的特点是低功耗。这一优点使AndroidApp可以与具有低...
  • Android BLE蓝牙开发

    2021-04-14 14:59:50
    @Android BLE蓝牙开发 蓝牙开发流程 第一步:声明所需要的权限 <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/...
  • 本文介绍了BLE开发所需要了解的基本概念和原理,理解了这些概念,开发BLE相关功能时,会更加顺畅。
  • Android Ble蓝牙开发(客户端)

    千次阅读 2017-08-31 16:08:08
    最近项目里面需要集成一个蓝牙的连接功能,作为一枚刚刚毕业不久的新生,大学几年又白过的。只好在几天内搜搜百度,脑补一下。文章部分内容摘至各大Blog,加上本dust的见解,写了一份Client端和Service端的小呆毛。...
  • 讲解低功耗蓝牙外围设备端程序开发的主要流程,包括低功耗蓝牙广播、初始化服务和特征值、监听设备连接情况、数据收发等
  • Android Ble蓝牙开发(服务器端)

    千次阅读 2017-09-03 12:06:20
    最近项目里面需要集成一个蓝牙的连接功能,作为一枚刚刚毕业不久的新生,大学几年又白过的。只好在几天内搜搜百度,脑补一下。文章部分内容摘至各大Blog,加上本dust的见解,写了一份Client端和Service端的小呆毛。...
  • Android Ble蓝牙开发

    千次阅读 2021-11-11 19:19:28
    @[TOP](BLE Android 开发) 1.权限设置 <uses-feature android:name="android.hardware.location.gps" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission ...
  • Android BLE 蓝牙开发框架

    千次阅读 2017-08-28 09:40:25
    Android Bluetooth Low Energy 蓝牙快速开发框架。 使用简单的方式进行搜索、连接、读写、通知的订阅与取消等一系列蓝牙操作,并实时地得到操作反馈。 Preview   Download dependency> groupId>...
  • Android手机必须系统版本4.3及以上才支持BLE API。通过GATT协议进行BLE设备之间的通信。 优点:传输速度更快,覆盖范围更广,安全性更高,延迟更短,耗电极低等等优点 BLE分为三部分:Service,Characteristic,...
  • 最近在做一个蓝牙智能锁的项目,需要在Android APP 上使用 Ble低功耗蓝牙 和单片机蓝牙设备进行通信,网上关于搜索 连接 读写数据的资料太多了,我在最后放了一个Demo,完整实现搜索 连接 读写数据,在文章结尾重点...
  • Android开发ble蓝牙

    2021-05-27 01:56:19
    前言由于自己工作中需要开发ble的项目,于是在折腾了一段时间后也有所了解,便想写下来分享给大家,同时对自己的知识也是一种巩固1.BLE介绍BLE是Bluetooth Low Energy的缩写,又叫蓝牙4.0,区别于蓝牙3.0和之前的技术。...
  • 最近公司开发需要用到蓝牙,花了大约一天的时间总结整理了一下。主要是为了以后自己方便看。有需要的朋友可以看下。欢迎一起讨论。后面会带上博客。里面是实现了蓝牙搜索,配对,连接,数据互传。
  • Android BLE 蓝牙开发

    2018-12-12 09:28:08
    前言: 随着物联网时代的到来,越来越多的智能硬件设备开始流行起来,比如智能手环、心率检测仪、...本文主要讲解Android低功耗蓝牙的api使用以及蓝牙扫描、连接、发送数据、接收数据等一系列操作,并主要介绍本...
  • android ble蓝牙开发

    2017-11-30 10:28:32
    1.获得蓝牙适配器 mBluetoothAdapter = mBluetoothManager.getAdapter();
  • android ble蓝牙开发略解

    万次阅读 多人点赞 2014-11-13 16:48:16
    Android 蓝牙4.0开发   1、 权限和相关属性 “android:required="true"表示apk只有在具有bluetooth_le属性的系统里运行,这个4.3之前android系统没有     2、 程序开妈操作蓝牙之前,先判断ble是否...
  • ####初始化 bleManager = BleManager.getInstance(); bleManager.init(this); ####扫描指定名称设备、并连接 bleManager.connect... ####其他 其他蓝牙操作可参考示例代码,或从BleManager这个类中开放的方法中找到。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,524
精华内容 1,809
关键字:

androidble蓝牙开发