BLE蓝牙(4.0)你要懂的基本使用

时间:2022-08-14 22:26:04

BLE蓝牙(4.0)你要懂的基本使用

每个人对于蓝牙都不陌生,近距离数据传输,方便;可是当你的业务需求需要你第一次接触蓝牙开发的时候,却会发现你对它并不了解;首先,蓝牙发展至今经历了8个版本的更新。1.1、1.2、2.0、2.1、3.0、4.0、4.1、4.2。那么在1.x~3.0之间的我们称之为传统蓝牙,4.x开始的蓝牙我们称之为低功耗蓝牙也就是蓝牙ble,当然4.x版本的蓝牙也是向下兼容的。android手机必须系统版本4.3及以上才支持BLE API。低功耗蓝牙较传统蓝牙,传输速度更快,覆盖范围更广,安全性更高,延迟更短,耗电极低等等优点。(现在的穿戴设备都是使用BLE蓝牙技术的)
传统蓝牙与低功耗蓝牙通信方式也有所不同,传统的一般通过socket方式,而低功耗蓝牙是通过Gatt协议来实现。所以我直接上手了BLE蓝牙。


BLE的三部分:Service,Characteristic,Descriptor

这三部分都用UUID作为唯一标识符。UUID为这种格式:0000ffe1-0000-1000-8000-00805f9b34fb。比如有3个Service,那么就有三个不同的UUID与Service对应。这些UUID都写在硬件里,我们通过BLE提供的API可以读取到。
一个BLE终端可以包含多个Service, 一个Service可以包含多个Characteristic,一个Characteristic包含一个value和多个Descriptor,一个Descriptor包含一个Value。Characteristic是比较重要的,是手机与BLE终端交换数据的关键,读取设置数据等操作都是操作Characteristic的相关属性。

如下图我有个BLE的硬件,用android 版本的 BLE Tool(点击下载蓝牙调试助手) 连接上,然后我们可以看到UUID列表,这里每一行的UUID都代表一个Service,再点击任意一行进去,又可以看到一个UUID列表,这里每一行的UUID都代表一个Characteristic,再点击任意一行进去,即可以操作这个Characteristic,比如写入数据或者读出数据等。

BLE蓝牙(4.0)你要懂的基本使用

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。通过这个类定义需要往外围设备写入的数据和读取外围设备发送过来的数据。

具体代码

百看不如敲一敲代码 必要步骤:

  • 权限申明
    <!--使用蓝牙所需要的权限-->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <!--使用扫描和设置蓝牙的权限(申明这一个权限必须申明上面一个权限)-->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <!--在 Android 6.0 及以上,还需要打开位置权限。如果应用没有位置权限,蓝牙扫描功能 不能使用-->
    <!-- 6.0位置权限需要动java代码中态获取-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <!--无交互的进行操作, API>=19添加 -->
    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
    <!--如果你想必须在支持ble 的设备上使用app 可以加上这一句-->
    <!--<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> --> 
  • BluetoothAdapter的获取
 BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
 BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
 if (null == adapter) {
    //蓝牙不支持
}
//还有一种获取方法
//BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
  • 判断当前蓝牙状态
if (mBluetoothAdapter.isEnabled()) {//判断是否打开蓝牙
    //mBluetoothAdapter.enable();//无交互打开蓝牙 

 //系统提示用户选择是否打开蓝牙
   startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_ENABLE);

}

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //代开蓝牙回调
        if (requestCode == REQUEST_ENABLE) {
            if (resultCode == RESULT_OK) {
               //打开成功
            } else {

                mlog.e("打开蓝牙失败");
            }
        }
    }
  • 开始扫描和停止扫描
扫描的方法有三种
1、是通过监听广播 mBluetoothAdapter.startDiscovery()
2、直接调用.startLeScan(BluetoothAdapter.LeScanCallbackcallback)即可扫描出BLE设备,在callback中会回调。
3、通过startScan(ScanCallbackcallback)

现在基本使用第二种方法

  // mBluetoothAdapter.startLeScan(mLeScanCallback); //开始搜索
  // mBluetoothAdapter.stopLeScan(mLeScanCallback);//停止搜索
  BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        //扫描结果回调 不要在这里处理耗时动作
            mlog.e(">>>>>>>发现设备 【" + device.getName() + "】 : " + device.getAddress());
        }
    };

//正确的扫描操作
  private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            scanHandler.postDelayed(new Runnable() {//10秒后停止扫描 不能长时间不间断的扫描 很好资源的
                @Override
                public void run() {
                    mScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);  //停止搜索 
                }
            }, SCAN_PERIOD); 
            mBluetoothAdapter.startLeScan(mLeScanCallback); //开始搜索
        } else {
            mBluetoothAdapter.stopLeScan(mLeScanCallback);//停止搜索
        }
    }
  • 连接蓝牙设备
 //连接
    public boolean connect(final String address) {
        if (mBluetoothAdapter == null || address == null) {
            mlog.e("BluetoothAdapter not initialized or unspecified address.");
            return false;
        }
        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        if (device == null) {
            mlog.e("??????Device not found. Unable to connect.");
            return false;
        }
        if (mBluetoothGatt != null) {
            mlog.e("???????");
            mBluetoothGatt.close();
            mBluetoothGatt = null;
        }
        mBluetoothGatt = device.connectGatt(getApplication(), false, mGattCallback); //该函数才是真正的去进行连接
// mBluetoothGatt.connect();
        mlog.e("Trying to create a new connection.");
        mBluetoothDeviceAddress = address;
        mConnectionState = STATE_CONNECTING;
        return true;
    }
  • 连接的回调
    private static UUID WR_SERVICE = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
    private static UUID READERID = UUID.fromString("0000fff4-0000-1000-8000-00805f9b34fb");
    private static UUID WRITEID = UUID.fromString("0000fff3-0000-1000-8000-00805f9b34fb");

BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);

            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    mlog.e("已连接");            
                    mConnectionState = STATE_CONNECTING;
                    mBluetoothGatt.discoverServices();//连上蓝牙就可以对服务进行查找发现
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:           
                    mlog.e("未连接");
                    mConnectionState = STATE_DISCONNECT;
                    mBluetoothGatt.close();//关键 如果断开蓝牙时没有释放资源会导致 133 错误

                    break;
            }

            if (status == BluetoothGatt.GATT_FAILURE) {
                mlog.e(status);
                Toast.makeText(getBaseContext(), "onConnectionStateChange status: " + status, Toast.LENGTH_LONG).show();
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            //mlog.e(" 发现服务");
           //打开读写服务
                BluetoothGattService readerService = mBluetoothGatt.getService(WRITESERVICE);
                //读的特征值
                BluetoothGattCharacteristic reader = readerService.getCharacteristic(READERID);
                //写的特征值
               BluetoothGattCharacteristic writer = readerService.getCharacteristic(WRITEID);

                for (BluetoothGattDescriptor descriptor : reader.getDescriptors()) {
                    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                    mBluetoothGatt.writeDescriptor(descriptor);
                }
                mBluetoothGatt.setCharacteristicNotification(reader, true);//打开通知 才能接收到数据回调
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            mlog.e("onCharacteristicRead>>>>>>" + characteristic);
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            //写入会有回调
            //数据写入结果回调 characteristic为你写入的指令 这可以判断数据是否完整写入

        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
         //这里接收到的就是蓝牙设备反馈回来的数据
            for (int i = 0; i < characteristic.getValue().length; i++) {
                Log.e("TAG", "------------获取数据 value[" + i + "]: " + characteristic.getValue()[i]);
            }

        }
//还有一些其他的回调基本用不上了
    };
  • 接下来就是数据的读写了
    读写数据主要还是要依据 蓝牙通讯协议 (这个硬件是会提供的)
    这里提供一个简单的示例:下图是蓝牙电子秤的置零指令
    BLE蓝牙(4.0)你要懂的基本使用
 //置零
    private byte[] buf_reset;

    public void zeroOdert() {
        //这里的 VERIFY是协议定义的指令校验 是把前面的数进行异或运算得到的值(具体要看协议怎么定义)
        byte VERIFY = 0;
        buf_reset = new byte[]{0x4C, 0x77, 0x06, 0x01, 0x72, VERIFY, 0x04, 0x1C};

        for (int i = 0; i < 5; i++) {
            VERIFY ^= buf_paramOder[i];
        }

        writer.setValue( buf_reset );
        mBluetoothGatt.writeCharacteristic( writer );
    }


//对应的反馈和读操作 以下是BluetoothGattCallback回调里的操作
  @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged( gatt, characteristic );

            int reres = characteristic.getValue()[5];
            if (reres == 0) {
                //置零成功
            } else if (reres == 1) {
                //置零失败
            }
        }

ok基本操作就这些了

注意点

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

  • 5、如果无法扫描请检查是否给了相应的权限

  • 6、蓝牙还有许多坑 所以尽量不要做自动化的操作 最好给出各种提示窗口一步步来 保证步骤的正确;蓝牙的稳定性和设备的性能也有一定的关系,还有尽量多的和蓝牙提供的硬件技术多沟通,能帮你省下不少时间!

推荐一个Log日志打印类 可以定位到具体代码 简直不能太方便{点击下载}

BLE蓝牙(4.0)你要懂的基本使用

人在年轻的时候,最头痛的是要解决这一生要做什么——by王小波