Android ble开发详解

前段时间,项目要接入一个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 就会收到一个监听的回调。
下面是一张来自官网的结构图
Android ble开发详解_第1张图片

更多关于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"/>  

连接设备

//先去获取BluetoothAdapter
 private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

//检查手机蓝牙开关、是否支持ble
 private void checkBluetooth() {
        //是否支持蓝牙功能
        if (mBluetoothAdapter == null) {
            return;
        }
        //是否支持BLE
        if (!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) {
            //开始搜索设备,搜索到设备会执行回调接口mLeScanCallback
            mBluetoothAdapter.startLeScan(mLeScanCallback);
            isScanning = true;
            //搜索设备十分耗电,应该避免长时间搜索,这里设置10s超时停止搜索
            mHandler.postDelayed(new Runnable() {
                @Override
                public 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() {
                @Override
                public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
                    //回调不是在ui线程中执行的,但是ble设备的连接、断开最好在ui线程中执行,否则可能会出现些奇奇怪怪的问题
                    ((Activity)mContext).runOnUiThread(new Runnable() {
                        @Override
                        public 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() {
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            //设备发送数据回调,由于ble设备可能有有多个characteristic
            //用于发送数据,这里进行比较,确认是否是我们想要的
            if (characteristic.equals(mReceiveCharac)) {
                //提取设备发送的数据
               Log.d(TAG, "接收到设备发送的数据:"+characteristic.getValue()); 
            }
        }
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            //mBluetoothGatt.DiscoverServices()的回调
            Log.d(TAG, "discovery service successfully");
            //以列表形式返回List mGattServices;
            mGattServices = gatt.getServices();
            //筛选保存我们需要的characteristic和service
            displayGattServices(mGattServices);
        }

        @Override
        public 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, "写入关机命令成功");
                }
            }
        }

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

        @Override
        public 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() {
                        @Override
                        public 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 gattServices) {
        Log.d(TAG, Thread.currentThread()+""+"display service and characteristics");
        if (gattServices == null) {
            return;
        }
        for (BluetoothGattService gattService : gattServices) { // 遍历出gattServices里面的所有服务
            //比较service的uuid,只取所需的service
            if (gattService.getUuid().equals(UUID.fromString(BATTERY_SERVICE_UUID))
                    || gattService.getUuid().equals(UUID.fromString(PRIVATE_SERVICE_UUID))) {
                List gattCharacteristics =
                        gattService.getCharacteristics();
                Log.d(TAG, "service uuid: " + gattService.getUuid());
                for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { //
                    // 遍历每条服务里的所有Characteristic,保存所需Characteristics
                    Log.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)) {
                            //接收数据characteristic
                        mReceiveCharac = gattCharacteristic;
                        //设置notification通知,按键回调在onCharacteristicChanged
                        //以下代码一定不能少,否则将无法接收到设备发送的数据
                        boolean isEnableNotification = mBluetoothGatt.setCharacteristicNotification(
                                mReceiveCharac, true);
                        if (isEnableNotification) {
                            List 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)) {
                            //写数据characteristic
                        mWriteCharac = 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, "读取电量");
        }
    }

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

 @Override
        public 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);           
        }
    }    

接着回调

  @Override
        public void onCharacteristicWrite(BluetoothGatt gatt,
                BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            //写入命令成功,且characteristic是我们所写入的characteristic
            if (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)) {
                         //接收数据的characteristic
                        mReceiveCharac = gattCharacteristic;
                        //设置notification通知属性,按键回调在onCharacteristicChanged
                        //以下代码一定不能少,否则将无法接收到设备发送的数据
                        boolean isEnableNotification = mBluetoothGatt.setCharacteristicNotification(
                                mReceiveCharac, true);
                        if (isEnableNotification) {
                            List 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设备按键按下时发送数据为例

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

总结

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

你可能感兴趣的:(Android学习笔记)