Android:BLE智能开发

目录

    • 前言
    • BLE是个什么
    • BLE中的角色分工
    • 主要的关键词和概念 
      • GATT(Generic Attribute Profile )
      • Characteristic
      • Service
    • Android如何使用BLE 
      • 蓝牙权限
      • APP和BLE外设交互流程
    • 后记 

 

前言

前些年,智能硬件炒的挺火的,那今天,咱就来说说智能硬件那些事。BLE是智能硬件的一种通讯方式,通过BLE连接,iOS & Android手机和智能硬件就可以进行自定义的交互了。交互的体验如何,很大程度上取决于智能硬件的驱动工程师驱动写的好不好,以及App的代码质量如何。

笔者曾参与过多款BLE智能硬件的开发,许久不用,怕忘了,把自己的整理的一些知识记录与此,同时也希望能够给一些同学带来帮助。本文将尽力向读者讲清楚BLE是什么,以及在实际Android开发中该如何使用BLE。

Android如何使用BLE

蓝牙权限

使用BLE需要两个权限

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

如果你想要APP只适配具备BLE的手机,那个可以再添加一个硬件权限特性

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

 

APP和BLE外设交互流程

APP和BLE外设交互的一个大概流程就是:

  1. BLE外设打开电源
  2. APP初始化蓝牙
  3. APP扫描周边BLE外设
  4. APP连接到周边BLE外设
  5. APP读写BLE外设
  6. 交互完成,APP向BLE外设写入关机/待机指令(可选)
  7. BLE外设关机
  8. APP关闭本地蓝牙连接

以下将逐步利用代码进行讲解APP和BLE外设交互.

 

 

 

初始化BLE

Java代码判断当前手机是否支持BLE低功耗蓝牙

// 判断手机是否支持BLE
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();// 如果手机不支持BLE就关闭程序,仅供参考
}

初始化蓝牙管理者和适配器,这2个对象是ble通讯的基石.

// 初始化蓝牙管理者和适配器,这2个对象是ble通讯的基石.
private BluetoothAdapter mBluetoothAdapter;
...
final BluetoothManager bluetoothManager =
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

跳转到系统蓝牙设置界面

private BluetoothAdapter mBluetoothAdapter;
...
// 验证蓝牙是否已打开,如果没打开就提示用户跳转打开.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }

 

 

 

APP扫描周边BLE外设

需要实现一个BluetoothAdapter.LeScanCallback回调接口,得到扫描结果。该接口只有一个回调方法:

/**
 * @param device 被手机蓝牙扫描到的BLE外设实体对象
 * @param rssi 大概就是表示BLE外设的信号强度,如果为0,则表示BLE外设不可连接。
 * @param scanRecord 被扫描到的BLE外围设备提供的扫描记录,一般没什么用
 */
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) 

由于扫描BLE设备比较消耗资源,官方推荐间歇性扫描,示例代码如下

private BluetoothAdapter mBluetoothAdapter;
    private boolean mScanning;
    private Handler mHandler;

    // 每扫描10s休息一下
    private static final long SCAN_PERIOD = 10000;

    private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
            // TODO 这里可以进行连接操作,连接操作见下一小节
            if (device != null && device.getName() != null && device.getName().contain("你的产品名称")){
                // 连接设备
                connectDevice(device);
                // 停止扫描
                scanLeDevice(false);
            }
           }
       });
   }
};

    ...
    /**
     * @param enable 是否进行扫描,false则停止扫描
     */
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            // 利用Handler进行间歇性扫描,每次扫描时间:10s
            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);
        }
        ...
    }

 

 

APP连接周边BLE外设

连接操作是进行手机和BLE外设交互的基础,请看下面connectDevice(BluetoothDevice)方法实现。

分两步走: 
1. 判断该设备是否连接过,连接过则首先尝试直接连接:BluetoothGatt.connect() 
2. 首次连接或者直连失败使用:BluetoothDevice.connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)

 

public boolean connectDevice(final BluetoothDevice device) {
        if (mBluetoothAdapter == null || device == null) {
            Log.w(TAG,
                    "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }

        String address = device.getAddress();
        // 之前连接过的设备,尝试直接连接。mBluetoothDeviceAddress表示刚才连接过的设备地址
        if (mBluetoothDeviceAddress != null
                && address.equals(mBluetoothDeviceAddress)
                && mBluetoothGatt != null) {
            Log.d(TAG,
                    "Trying to use an existing mBluetoothGatt for connection.");
            if (mBluetoothGatt.connect()) {// 连接成功
                // 修改连接状态变量
                mConnectionState = STATE_CONNECTING;
                return true;
            } else {
                return false;
            }
        }

        final BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(address);
        if (remoteDevice == null) {
            Log.w(TAG, "Device not found.  Unable to connect.");
            return false;
        }
        mBluetoothGatt = remoteDevice.connectGatt(context, false, mGattCallback);
        Log.d(TAG, "Trying to create a new connection.");
        // 将当前连接上的设备地址赋值给连接过的设备地址变量
        mBluetoothDeviceAddress = address;
        // 改变连接状态变量
        mConnectionState = STATE_CONNECTING;
        return true;
    }

连接BEL外设时,需要一个实现回调接口以得到连接状态,BluetoothGattCallback大概实现如下:

private final BluetoothGattCallback mGattCallback =
            new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            // 该方法在连接状态改变时回调,newState即代表当前连接状态
            String intentAction;
            // 连接上了
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                intentAction = ACTION_GATT_CONNECTED;
                // 改变蓝牙连接状态变量
                mConnectionState = STATE_CONNECTED;
                // 发送自定义广播:连接上了
                broadcastUpdate(intentAction);
                // 当前外设相当于前面章节提到的Server角色:提供数据被手机读取
                Log.i(TAG, "Connected to GATT server.");
                // 获取读/写服务:Service。该方法会触发下面的onServicesDiscovered()回调
                mBluetoothGatt.discoverServices();

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 断开连接了
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;
                Log.i(TAG, "Disconnected from GATT server.");
                // 发送自定义广播:断开了连接
                broadcastUpdate(intentAction);
            }
        }

        @Override
        // 该方法在蓝牙服务被发现时回调。由上述的mBluetoothGatt.discoverServices()触发结果。
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            // 发现服务。status表示发现服务的结果码
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
                // TODO 从发现的Service来找出读数据用的BluetoothGattCharacteristic和写数据用的BluetoothGattCharacteristic。
                initReadAndWriteCharacteristic(gatt.getServices());

            } else {// 未发现服务
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        // 读取操作的回调结果
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }

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

 

 

 

找出读写”数据包”的”搬运工”

下面是找出读写”搬运工”BluetoothGattCharacteristic的initReadAndWriteCharacteristic()代码实现

BluetoothGattCharacteristic mReadCharacteristic;
BluetoothGattCharacteristic mWriteCharacteristic;

public void initReadAndWriteCharacteristic(
            List<BluetoothGattService> gattServices) {
        if (gattServices == null)
            return;
        // 遍历所有的 GATT Services.
        for (BluetoothGattService gattService : gattServices) {
            if (!gattService.getUuid().toString().trim().equalsIgnoreCase("这里是你期望的Service的uuid,由你司智能外色的驱动工程师决定"))
                continue;
            List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
            // 遍历当前Service中所有的Characteristics.
            for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {

                if (gattCharacteristic.getUuid().toString().trim().equalsIgnoreCase(""这里是你期望的写数据的uuid,由你司驱动工程师决定"")) {
                    mWriteCharacteristic = gattCharacteristic;
                } else if (gattCharacteristic.getUuid().toString().trim().equalsIgnoreCase("这里是你期望的读数据的uuid,由你司驱动工程师决定")) {
                    mReadCharacteristic = gattCharacteristic;
                }
            }
        }
    }

至此,我们就拿到了可携带读写数据的“搬运工”-『mReadCharacteristic & mWriteCharacteristic』,下面就可以和智能硬件进行交互了。 

 

 

APP读取BLE外设蓝牙数据

想要读取BLE外设的数据时,比如:心跳速率,电量等等。可通过下面方式。

// 告诉”搬运工“我想知道BLE外设当前数据,将回调BluetoothGattCallback接口的onCharacteristicRead()方法
mBluetoothGatt.readCharacteristic(mReadCharacteristic);

// 读取BLE蓝牙数据操作的回调方法
 @Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
                // ”搬运工“把”数据包“搬出来了
                byte[] data = characteristic.getValue();
                // 根据驱动工程师给的协议文档,解析该数组,该处假设数组0位上表示心跳速率
                int heartRateR = data[0];// 得到心跳速率,做相应UI更新和操作
        }
            }
        }

 

 

APP向BLE外设写入数据

比如说你想告诉BLE外设让他锁屏,或者进行某个动作,APP向操纵BLE外设时可通过以下方式

// 根据驱动工程师给的协议文档,组织一个数组命令
byte[] data = getData();
// 将该条命令“数据包”给“搬运工"
mWriteCharacteristic.setValue(data);

// ”搬运工“将数据搬到BLE外设里面了,将回调BluetoothGattCallback接口的onCharacteristicWrite()方法
mBluetoothGatt.writeCharacteristic(characteristic);

// 向BLE蓝牙外设写入数据操作的回调方法
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if(status == BluetoothGatt.GATT_SUCCESS) {
                // 命令写入成功,数据包成功写入BLE外设中
            }   
        };

  

 

APP关闭蓝牙连接

交互完了,不需要了,还是把APP蓝牙连接给断掉吧

public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;
}

  

 

 

注意事项

在Android 6.0+搜索蓝牙是需要定位权限的,还有BLE搜索在Android 5.0以前和以后是不一样的。最后你还会发现使用官方这套搜索在一些手机型号上也是搜不到的!只能通过传统蓝牙(非BLE方式)搜索然后过滤出BLE设备。

 

本文作者MichaelX,博客地址:http://blog.csdn.net/xiong_it 转载请注明来源

posted @ 2018-08-17 10:50  HealthTree  阅读(256)  评论(0编辑  收藏  举报