之前在一家公司做过关于蓝牙低功耗的功能,今天有空记录一下。
蓝牙低功耗(Bluetooth Low Energy)是从Android 4.3 (API level
18)新增的,相对于传统来说功耗相对低一些。先来了解一波理论~



关键术语和概念:

* Generic Attribute Profile (GATT)
GATT配置文件是通过BLE链接发送和接收称为“属性”的短数据段的通用规范。 所有当前的低能耗应用程序配置文件均基于GATT。
蓝牙SIG为低能耗设备定义了许多配置文件。 配置文件是设备如何在特定应用程序中工作的规范。 请注意,设备可以实现多个配置文件。
例如,一个设备可能包含一个心率监测器和一个电池电量检测器。
* Attribute Protocol (ATT)
GATT建立在属性协议(ATT)之上。 这也被称为GATT / ATT。 ATT经过优化,可在BLE设备上运行。 为此,它使用尽可能少的字节。
每个属性由通用唯一标识符(UUID)唯一标识,该标识符是用于唯一标识信息的字符串ID的标准化128位格式。 由ATT传输的属性被格式化为特征和服务。
* Characteristic
一个特征包含描述特征值的单个值和0-n个描述符。 一个特征可以被认为是一种类型,类似于一个阶级。
* Descriptor
描述符是描述特征值的定义属性。 例如,描述符可以指定一个人类可读的描述,一个特征值的可接受范围,或特征值特定的度量单位。
*
Service
服务是一系列特征。 例如,您可以使用名为“心率监视器”的服务,其中包含“心率测量”等特征。

*
蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"/>
如果您想声明您的应用仅适用于支持BLE的设备,请在应用的清单中包含以下内容:
<uses-feature android:name="android.hardware.bluetooth_le" android:required=
"true"/>
但是,如果您想让您的应用适用于不支持BLE的设备,则应该在应用的清单中包含此元素,但设置required =“false”。
然后在运行时,您可以使用PackageManager.hasSystemFeature()来确定BLE可用性:
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE
_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH
_SHORT).show(); finish(); }
1.获取蓝牙适配器BluetoothAdapter
private BluetoothAdapter mBluetoothAdapter; ... // Initializes Bluetooth
adapter. final BluetoothManager bluetoothManager = (BluetoothManager)
getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter =
bluetoothManager.getAdapter();
2.打开蓝牙
//确保蓝牙在设备上可用并且已启用。 如果不, //显示一个对话框,请求用户启用蓝牙的权限。 if (mBluetoothAdapter == null
|| !mBluetoothAdapter.isEnabled()) { Intent enableBtIntent =new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }

注意:传递给startActivityForResult(android.content.Intent,int)的REQUEST_ENABLE_BT常量是系统在onActivityResult(int,int,android.content)中传回给您的本地定义的整数(它必须大于0)。
意图)实现作为requestCode参数。

3.扫描蓝牙设备

要找到BLE设备,请使用startLeScan()方法。 此方法将BluetoothAdapter.LeScanCallback作为参数。
您必须实现此回调,因为这是如何返回扫描结果。 由于扫描耗电量大,您应遵守以下准则:

一旦找到所需的设备,请停止扫描。
切勿扫描循环,并在扫描上设置时间限制。 之前可用的设备可能已移出范围,并继续扫描电池电量。
注意:6.0的需要加入ACCESS_COARSE_LOCATION 和ACCESS_FINE_LOCATION这2个权限扫描才会返回结果哦!
/** * Activity for scanning and displaying available BLE devices. */ public
class DeviceScanActivity extends ListActivity { private BluetoothAdapter
mBluetoothAdapter;private boolean mScanning; private Handler mHandler; // Stops
scanning after 10 seconds. private static final long SCAN_PERIOD = 10000; ...
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); } ... } ... }
如果您只想扫描特定类型的外设,您可以改为调用startLeScan(UUID
[],BluetoothAdapter.LeScanCallback),提供一组UUID对象,以指定应用程序支持的GATT服务。

以下是BluetoothAdapter.LeScanCallback的一个实现,它是用于传递BLE扫描结果的接口:
private LeDeviceListAdapter mLeDeviceListAdapter; ... // Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback = new
BluetoothAdapter.LeScanCallback() {@Override public void onLeScan(final
BluetoothDevice device,int rssi, byte[] scanRecord) { runOnUiThread(new
Runnable() {@Override public void run() {
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged(); } }); } };
注意:您只能扫描蓝牙LE设备或扫描经典蓝牙设备,如蓝牙中所述。 您无法同时扫描Bluetooth LE和传统设备。

4.连接到GATT服务器

与BLE设备交互的第一步是连接到它 - 更具体地说,连接到设备上的GATT服务器。
要连接到BLE设备上的GATT服务器,请使用connectGatt()方法。
该方法有三个参数:一个Context对象,autoConnect(布尔值,指示是否在BLE设备变为可用时自动连接)以及对BluetoothGattCallback的引用:
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
它连接到由BLE设备托管的GATT服务器,并返回一个BluetoothGatt实例,然后您可以使用该实例来执行GATT客户端操作。
调用者(Android应用程序)是GATT客户端。
BluetoothGattCallback用于向客户端传递结果,例如连接状态以及任何进一步的GATT客户端操作。
连接成功后,ble的各种状态会在mGattCallback这个对象中回掉,举个栗子:
// A service that interacts with the BLE device via the Android BLE API. public
class BluetoothLeService extends Service { private final static String TAG =
BluetoothLeService.class.getSimpleName();private BluetoothManager
mBluetoothManager;private BluetoothAdapter mBluetoothAdapter; private String
mBluetoothDeviceAddress;private BluetoothGatt mBluetoothGatt; private int
mConnectionState = STATE_DISCONNECTED;private static final int
STATE_DISCONNECTED =0; private static final int STATE_CONNECTING = 1; private
static final int STATE_CONNECTED = 2; public final static String
ACTION_GATT_CONNECTED ="com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public
final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; public final static String
ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; public final static
String ACTION_DATA_AVAILABLE ="com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA";
public final static UUID UUID_HEART_RATE_MEASUREMENT =
UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);// BLE
API定义的各种回调方法。 private final BluetoothGattCallback mGattCallback = new
BluetoothGattCallback() {@Override //连接状态改变的方法 public void
onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { String
intentAction;if (newState == BluetoothProfile.STATE_CONNECTED) { intentAction =
ACTION_GATT_CONNECTED; mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction); Log.i(TAG,"Connected to GATT server.");
Log.i(TAG,"Attempting to start service discovery:" +
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
//发现新服务(一般来说,发现服务后才可以遍历可用的特征和描述之类的信息) public void onServicesDiscovered
(BluetoothGatt gatt,int status) { if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); }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); } } ... }; ... }
发送广播
private void broadcastUpdate(final String action) { final Intent intent = new
Intent(action); sendBroadcast(intent); }private void broadcastUpdate(final
String action,final BluetoothGattCharacteristic characteristic) { final Intent
intent =new Intent(action); // This is special handling for the Heart Rate
Measurement profile. Data // parsing is carried out as per profile
specifications. if
(UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {int flag =
characteristic.getProperties();int format = -1; if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16; Log.d(TAG,"Heart rate
format UINT16."); } else { format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG,"Heart rate format UINT8."); } final int heartRate =
characteristic.getIntValue(format,1); Log.d(TAG, String.format("Received heart
rate: %d", heartRate)); intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
}else { // For all other profiles, writes the data formatted in HEX. final byte
[] data = characteristic.getValue();if (data != null && data.length > 0) { final
StringBuilder stringBuilder =new StringBuilder(data.length); for(byte byteChar
: data) stringBuilder.append(String.format("%02X ", byteChar));
intent.putExtra(EXTRA_DATA,new String(data) + "\n" + stringBuilder.toString());
} } sendBroadcast(intent); }
接收广播
// 处理由服务发起的各种事件。 // ACTION_GATT_CONNECTED:连接到GATT服务器。 //
ACTION_GATT_DISCONNECTED:与GATT服务器断开连接。 //
ACTION_GATT_SERVICES_DISCOVERED:发现GATT服务。 // ACTION_DATA_AVAILABLE:收到来自设备的数据。
这可能是一个 //读取或通知操作的结果。 private final BroadcastReceiver mGattUpdateReceiver = new
BroadcastReceiver() { @Overridepublic void onReceive(Context context, Intent
intent) { finalString action = intent.getAction(); if (BluetoothLeService.
ACTION_GATT_CONNECTED.equals(action)) { mConnected = true;
updateConnectionState(R.string.connected); invalidateOptionsMenu(); } else if
(BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { mConnected =
false; updateConnectionState(R.string.disconnected); invalidateOptionsMenu();
clearUI(); }else if (BluetoothLeService. ACTION_GATT_SERVICES_DISCOVERED.equals
(action)) {// Show all the supported services and characteristics on the //
user interface. displayGattServices(mBluetoothLeService.
getSupportedGattServices()); }else if (BluetoothLeService.ACTION_DATA_AVAILABLE.
equals(action)) { displayData(intent.getStringExtra(BluetoothLeService.
EXTRA_DATA)); } } };
读取BLE属性
public class DeviceControlActivity extends Activity { ... /** * 遍历服务和特征 * *
@param gattServices 通过调用mBluetoothGatt.getServices(); */ private void
displayGattServices(List<BluetoothGattService> gattServices) { if (gattServices
== null) return; String uuid = null; String unknownServiceString =
getResources(). getString(R.string.unknown_service); String unknownCharaString =
getResources(). getString(R.string.unknown_characteristic); ArrayList<HashMap<
String, String>> gattServiceData = new ArrayList<HashMap<String, String>>();
ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new
ArrayList<ArrayList<HashMap<String, String>>>(); mGattCharacteristics = new
ArrayList<ArrayList<BluetoothGattCharacteristic>>(); // Loops through available
GATT Services. for (BluetoothGattService gattService : gattServices) { HashMap<
String, String> currentServiceData = new HashMap<String, String>(); uuid =
gattService.getUuid().toString(); currentServiceData.put( LIST_NAME,
SampleGattAttributes. lookup(uuid, unknownServiceString)); currentServiceData.
put(LIST_UUID, uuid); gattServiceData.add(currentServiceData); ArrayList<HashMap
<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String,
String>>(); List<BluetoothGattCharacteristic> gattCharacteristics = gattService.
getCharacteristics(); ArrayList<BluetoothGattCharacteristic> charas = new
ArrayList<BluetoothGattCharacteristic>(); // Loops through available
Characteristics. for (BluetoothGattCharacteristic gattCharacteristic :
gattCharacteristics) { charas.add(gattCharacteristic); HashMap<String, String>
currentCharaData= new HashMap<String, String>(); uuid = gattCharacteristic.
getUuid().toString(); currentCharaData.put( LIST_NAME, SampleGattAttributes.
lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid);
gattCharacteristicGroupData.add(currentCharaData); } mGattCharacteristics.
add(charas); gattCharacteristicData.add(gattCharacteristicGroupData); } ... }
... }
接收GATT通知

BLE应用程序在设备上发生特定特征变化时要求收到通知是很常见的。设置了setCharacteristicNotification()方法,mGattCallback中的onCharacteristicChanged()方法才会回调!
这段代码展示了如何使用setCharacteristicNotification()方法为特征设置通知:
private BluetoothGatt mBluetoothGatt; BluetoothGattCharacteristic
characteristic; boolean enabled;...
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);...
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
一旦为特征启用通知,如果特性在远程设备上发生变化,则会触发onCharacteristicChanged()回调:
@Override // Characteristic notification public void onCharacteristicChanged
(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); }
断开客户端
public void disconnect() { if (mBluetoothGatt == null) { return; }
mBluetoothGatt.disconnect(); }
关闭客户端应用程序
一旦您的应用程序完成使用BLE设备,它应该调用close(),以便系统可以适当地释放资源:
public void close() { if (mBluetoothGatt == null) { return; }
mBluetoothGatt.close(); mBluetoothGatt =null; }
OK,到这里一个简单的BLE使用例子就完成了。下篇文章就是蓝牙进阶了,将介绍一些在实际开发中遇到的坑,以及如何填坑。


友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信