BLE蓝牙开发入门
蓝牙低能耗(Bluetooth Low Energy,或称Bluetooth LE、BLE,旧商标Bluetooth Smart)也称低功耗蓝牙,是蓝牙技术联盟设计和销售的一种个人局域网技术,旨在用于医疗保健、运动健身、信标、安防、家庭娱乐等领域的新兴应用。相较经典蓝牙,低功耗蓝牙旨在保持同等通信范围的同时显著降低功耗和成本。
Android BLE 的特点是低功耗,其峰值功耗为传统蓝牙的一半,传输距离提升到100米左右,最短可在3ms内完成连接并开始数据的传输,缺点是传输的数据量较小,最大512个字节,超过20个字节需要分包处理。
基本概念
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 就会收到一个监听的回调。
DesCriptor
任何在特性中的属性不是定义为属性值就是为描述符。描述符是一个额外的属性以提供更多特性的信息,它提供一个人类可识别的特性描述的实例。然而,有一个特别的描述符值得特别地提起:客户端特性配置描述符(Client Characteristic Configuration Descriptor,CCCD),这个描述符是给任何支持通知或指示功能的特性额外增加的。在CCCD中写入“1”使能通知功能,写入“2”使能指示功能,写入“0”同时禁止通知和指示功能。
BluetoothAdapter
本地蓝牙适配器,是所有蓝牙交互的入口点,表示蓝牙设备自身的一个蓝牙设备适配器,整个系统只有一个蓝牙适配器。通过它可以发现其他蓝牙设备,查询绑定(配对)设备的列表,使用已知的 Mac 地址实例化 BluetoothDevice
以及创建 BluetoothServerSocket
用来侦听来自其他设备的通信。
BluetoothDevice
代表一个远程蓝牙设备。这个类可以让你连接所代表的蓝牙设备或者获取一些有关它的信息,例如它的名字,地址和绑定状态等等。
BluetoothGatt
这个类提供了 Bluetooth GATT 的基本功能。例如重新连接蓝牙设备,发现蓝牙设备的 Service 等等。
BluetoothGattService
这一个类通过 BluetoothGatt#getService 获得,如果当前服务不可见那么将返回一个 null。这一个类对应上面说过的 Service。我们可以通过这个类的 getCharacteristic(UUID uuid) 进一步获取 Characteristic 实现蓝牙数据的双向传输。
蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
在Android5.0之前,是默认申请GPS硬件功能的。而在Android 5.0 之后,需要在manifest 中申明GPS硬件模块功能的使用。
<uses-feature android:name="android.hardware.location.gps" />
在 Android 6.0 及以上,还需要打开位置权限。如果应用没有位置权限,蓝牙扫描功能不能使用(其它蓝牙操作例如连接蓝牙设备和写入数据不受影响)。
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
BLE功能确认
// 检查当前手机是否支持ble 蓝牙,如果不支持退出程序 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { finish(); } // 初始化 Bluetooth adapter, 通过蓝牙管理器得到一个参考蓝牙适配器(API必须在以上android4.3或以上和版本) final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); // 检查设备上是否支持蓝牙 if (mBluetoothAdapter == null) { return; }
启用设备蓝牙功能
// 为了确保设备上蓝牙能使用, 如果当前蓝牙设备没启用,弹出对话框向用户要求授予权限来启用 if (!mBluetoothAdapter.isEnabled()) { if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } }
开启蓝牙扫描
在 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);
}
};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 方法进行重新连接
1552 /**
1553 * Connect to GATT Server hosted by this device. Caller acts as GATT client.
1554 * The callback is used to deliver results to Caller, such as connection status as well
1555 * as any further GATT client operations.
1556 * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
1557 * GATT client operations.
1558 * @param callback GATT callback handler that will receive asynchronous callbacks.
1559 * @param autoConnect Whether to directly connect to the remote device (false)
1560 * or to automatically connect as soon as the remote
1561 * device becomes available (true).
1562 * @throws IllegalArgumentException if callback is null
1563 */
1564 public BluetoothGatt connectGatt(Context context, boolean autoConnect,
1565 BluetoothGattCallback callback) {
1566 return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO));
1567 }
-
autoConnect:表示是否需要自动连接。如果设置为 true, 表示如果设备断开了,会不断的尝试自动连接。设置为 false 表示只进行一次连接尝试。
- BluetoothGattCallback:是连接后进行的一系列操作的回调,例如连接和断开连接的回调,发现服务的回调,成功写入数据,成功读取数据的回调等等。
BluetoothGatt的connect接口:
680 /**
681 * Connect back to remote device.
682 *
683 * <p>This method is used to re-connect to a remote device after the
684 * connection has been dropped. If the device is not in range, the
685 * re-connection will be triggered once the device is back in range.
686 *
687 * @return true, if the connection attempt was initiated successfully
688 */
689 public boolean connect() {
690 try {
691 mService.clientConnect(mClientIf, mDevice.getAddress(),
692 false, mTransport); // autoConnect is inverse of "isDirect"
693 return true;
694 } catch (RemoteException e) {
695 Log.e(TAG,"",e);
696 return false;
697 }
698 }
/** * Connects to the GATT server hosted on the Bluetooth LE device. * * @param address The device address of the destination device. * * @return Return true if the connection is initiated successfully. The connection result * is reported asynchronously through the * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} * callback. */ public boolean connect(final String address) { if (mBluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } // Previously connected device. Try to reconnect. 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 device = mBluetoothAdapter.getRemoteDevice(address); if (device == null) { Log.w(TAG, "Device not found. Unable to connect."); return false; } // We want to directly connect to the device, so we are setting the autoConnect // parameter to false. mBluetoothGatt = device.connectGatt(this, false, mGattCallback); Log.d(TAG, "Trying to create a new connection."); mBluetoothDeviceAddress = address; mConnectionState = STATE_CONNECTING; return true; }
当调用蓝牙的连接方法之后,蓝牙会异步执行蓝牙连接的操作,如果连接成功会回调 BluetoothGattCalbackl#onConnectionStateChange 方法。这个方法运行的线程是一个 Binder 线程,所以不建议直接在这个线程处理耗时的任务,因为这可能导致蓝牙相关的线程被阻塞。
24 /**
25 * Callback indicating when GATT client has connected/disconnected to/from a remote
26 * GATT server.
27 *
28 * @param gatt GATT client
29 * @param status Status of the connect or disconnect operation.
30 * {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
31 * @param newState Returns the new connection state. Can be one of
32 * {@link BluetoothProfile#STATE_DISCONNECTED} or
33 * {@link BluetoothProfile#STATE_CONNECTED}
34 */
35 public void onConnectionStateChange(BluetoothGatt gatt, int status,
36 int newState) {
37 }
-
gatt:蓝牙设备的 Gatt 服务连接类。
-
status:是否成功执行了连接操作,如果为 BluetoothGatt.GATT_SUCCESS 表示成功执行连接操作,第三个参数才有效,否则说明这次连接尝试不成功。有时候,我们会遇到 status == 133 的情况,根据网上大部分人的说法,这是因为 Android 最多支持连接 6 到 7 个左右的蓝牙设备,如果超出了这个数量就无法再连接了。所以当我们断开蓝牙设备的连接时,还必须调用 BluetoothGatt#close 方法释放连接资源。否则,在多次尝试连接蓝牙设备之后很快就会超出这一个限制,导致出现这一个错误再也无法连接蓝牙设备。
-
newState:当前设备的连接状态,如果 newState == BluetoothProfile.STATE_CONNECTED 说明设备已经连接,可以进行下一步的操作了(发现蓝牙服务,也就是 Service)。当蓝牙设备断开连接时,这一个方法也会被回调,其中的 newState == BluetoothProfile.STATE_DISCONNECTED。
发现服务
BluetoothGattCalbackl#onConnectionStateChang 方法被成功回调且表示成功连接之后调用 BluetoothGatt#discoverService 这一个方法。当这一个方法被调用之后,系统会异步执行发现服务的过程,直到 BluetoothGattCallback#onServicesDiscovered 被系统回调之后,手机设备和蓝牙设备才算是真正建立了可通信的连接。
39 /**
40 * Callback invoked when the list of remote services, characteristics and descriptors
41 * for the remote device have been updated, ie new services have been discovered.
42 *
43 * @param gatt GATT client invoked {@link BluetoothGatt#discoverServices}
44 * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device
45 * has been explored successfully.
46 */
47 public void onServicesDiscovered(BluetoothGatt gatt, int status) {
48 }
@Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { } else { Log.w(TAG, "onServicesDiscovered received: " + status); } }
读取数据
当我们发现服务之后就可以通过 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 = service.getCharacteristic(CHARACTER_UUID);
gatt.readCharacteristic(characteristic);
发送数据
public static void sendMessage(){ BluetoothGattService service=mBluetoothGatt.getService(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); BluetoothGattCharacteristic charaWrite=service.getCharacteristic(CLIENT_SEND_UUID); //。。。 mBluetoothGatt.writeCharacteristic(charaWrite); mBluetoothGatt.setCharacteristicNotification(charaWrite, true); }
向蓝牙设备注册监听
除了通过 BluetoothGatt#setCharacteristicNotification 开启 Android 端接收通知的开关,还需要往 Characteristic 的 Descriptor 属性写入开启通知的数据开关使得当硬件的数据改变时,主动往手机发送数据。
final boolean enableNotifications(final BluetoothGattCharacteristic characteristic) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || characteristic == null) return false; // Check characteristic property final int properties = characteristic.getProperties(); if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0) return false; gatt.setCharacteristicNotification(characteristic, true); final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); if (descriptor != null) { descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); return gatt.writeDescriptor(descriptor); } return false; }
断开连接
当我们连接蓝牙设备完成一系列的蓝牙操作之后就可以断开蓝牙设备的连接了。通过 BluetoothGatt#disconnect 可以断开正在连接的蓝牙设备。当这一个方法被调用之后,系统会异步回调 BluetoothGattCallback#onConnectionStateChange 方法。通过这个方法的 newState 参数可以判断是连接成功还是断开成功的回调。
由于 Android 蓝牙连接设备的资源有限,当我们执行断开蓝牙操作之后必须执行 BluetoothGatt#close 方法释放资源。需要注意的是通过 BluetoothGatt#close 方法也可以执行断开蓝牙的操作,不过 BluetoothGattCallback#onConnectionStateChange 将不会收到任何回调。此时如果执行 BluetoothGatt#connect 方法会得到一个蓝牙 API 的空指针异常。所以,我们推荐的写法是当蓝牙成功连接之后,通过 BluetoothGatt#disconnect 断开蓝牙的连接,紧接着在 BluetoothGattCallback#onConnectionStateChange 执行 BluetoothGatt#close 方法释放资源。
以上大部分来自:https://www.jianshu.com/p/3a372af38103 ,并结合自己的项目做了部分修改,最后感谢原文博主!