Android Bluetooth Low Energy

本章主要内容

  1. Key Terms and Concepts(关键术语和概念)
    1. Roles and Responsibilities(角色和职责)
  2. BLE Permissions(BLE 权限)
  3. Setting Up BLE(BLE 设置)
  4. Finding BLE Devices(查找 BLE 设备)
  5. Connecting to a GATT Server(连接到一个 GATT Server)
  6. Reading BLE Attributes(读取 BLE 属性)
  7. Receiving GATT Notifications(接受 GATT 通知)
  8. Closing the Client App(关闭 Client App)

关键类

  1. BluetoothGatt
  2. BluetoothGattCallback
  3. BluetoothGattCharacteristic
  4. BluetoothGattService

相关案例(可到 github 上搜)

  1. Bluetooth LE sample

SEE ALSO

  1. Best Practices for Bluetooth Development (video)

    Android 4.3 (API Level 18) 引入了内置的用于支持 BLE( Bluetooth Low Energy) 的平台,它提供了用于发现设备、查询服务和 read/write 特征等功能的 APIs 。和传统的 Blutooth 不同,BLE 是为了有低功耗要求的设备(如传感器、心率监视器、健身设备等)而设计的 。
    
1. 关键术语和概念
    下面是关于 BLE 的关键术语和概念简介:
    1)Generic Attribute Profile (GATT):GATT profile 是发送和接收简短数据的通用规范,当前所有的 Low Energy app profiles 都是基于 GATT 的 。Bluetooth SIG 为 Low Energy 设备定义了许多 profiles,一个 profile 规定了一个设备如何在特定的 app 环境下工作 。注意一个设备可以实现多种 profile,如一个设备可以同时包含一个心率监视器和一个电量探测器 。
    2)Attribute Protocol (ATT):GATT 是在 ATT 之上建立起来的,所以也常称之为 GATT/ATT 。ATT 是运行于 BLE 设备上的最优化的 profile 。为了达到最优化的目的,ATT 尽量减少了它所使用的字节数,它的每一个属性都由 UUID 来唯一标识 。ATT 所传输的所有 attributes 都会被格式化成 characteristics 和 services 。
    3)Characteristic:一个 characteristic 包含单一的一个值和 0-n 个用于描述 characteristic 的值的描述符(descriptors),可以把一个 characteristic 看成是一种类型,类似于一个类 。
    4)Descriptor:descriptors 用于定义描述特征值的属性(即 descriptors 用于定义属性,而属性用于描述特征值)。
    5)Service:一个 service 是一个 characteristics 的集合 。如,你可能会有一个名这 ”Heart Rate Monitor“ 的 service,它包含了如 ”心率测量“ 之类的 characteristics 。你可以到 bluetooth.org 查看一些已存在的基于GATT 的 profiles 和 services 。

2. Roles and Responsibilities
    下面是当 Android 和 BLE 设备交互时会涉及到的一些 roles 和 responsibilities:
    1)Central vs. peripheral:这主要应用于 BLE 连接本身,充当 central 角色的设备,会扫描、查找其它设备发布的 advertisement;而充当 peripheral 角色的设备,会为本设备发布 advertisement(即向别的设备宣告自己的存在);
    2)GATT server vs. GATT client:这决定了当两设备建立连接后,它们如何与对方进行通信 。
    为了理解这些角色和职责的区别,你可以设想有一个 Android phone 和 一个作为 BLE 设备的 activity tracker(活动追踪器)。phone 充当 central 角色,而 activity tracker 充当 peripheral 角色(如果要建立一个 BLE 的连接,那么这两种角色都是必不可少的,只有其中任何一种角色都是没法相互连接的)。
    一旦 phone 和 activity tracker 建立起连接,它们就可以与对方进行 GATT 元数据的传输了 。两设备中,哪个充当 server 取决于它们所传输的数据类型,如当 activity tracker 要向 phone 传输 sensor 数据时,activity tracker 就充当 server;而当 activity tracker 想要接收到自 phone 的更新信息时,phone 就充当 server 。
    在本章所使用的例子中,Android app 充当 GATT client,它从一个充当 GATT server 的 BLE 心率监视器接收数据 。

3. BLE Permissions
    如果你想使用 Bluetooth 功能,那么你就得声明 BLUETOOTH 权限,没有这个权限,你就没法进行任何与 Bluetooth 相关的通信 。
    如果你想要 app 能够初始化设备 discovery 或操作 Bluetooth 设置,那么你还得声明 BLUETOOTH_ADMIN 权限,注意如果你声明了 BLUETOOTH_ADMIN 权限,那么就一定要再声明 BLUETOOTH 权限 。如下
<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 的设备上,那么你还是要包含上边这个声明,只要把 required 设置为 false 即可 。然后在运行时,你就可以通过  PackageManager.hasSystemFeature() 来判断 BLE 是否可用 。
// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
   
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish
();
}

4. Setting Up BLE
    在 app 通过 BLE 进行通信前,你得先确认一下设备是否支持 BLE,如果支持,那么还要保证它已被使能了 。注意:只有 <uses-feature.../> 设置 false 才需要这个检查 。
    如果设备不支持 BLE,那么你就得禁用 BLE 的特性了 。如果设备支持 BLE,但是没有被使能,那么你可以在不关掉 app 的前提下提示用户使能它,这可以通过两个步骤来完成:
    1)获取 BluetoothAdapter 对象
    对于整个系统来说,只有一个 Bluetooth adapter 对象,app 可通过 BluetoothAdapter 对象来与之交互 。如下的代码片段演示了如何获取到 adapter 对象,注意它是先通过 getSystemService() 获取到一个 BluetoothManager 实例,然后再通过这个实例去获取 adapter(Android 4.3 引进了BluetoothManager)
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
       
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter
= bluetoothManager.getAdapter();
    2)Enable Bluetooth(使能 Bluetooth
private BluetoothAdapter mBluetoothAdapter;
...
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
   
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult
(enableBtIntent, REQUEST_ENABLE_BT);
}
5. 查找 BLE 设备
    可通过 startLeScan() 方法去查找 BLE 设备,此方法需要一个 BluetoothAdapter.LeScanCallback 参数 。必须实现这个回调,因为它要用于返回扫描结果 。由于扫描操作是很耗电的,所以在扫描时要遵循以下原则:
    1)在发现目标设备后,立即停止扫描;
    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);
       
}
       
...
   
}
...
}
    如果你只想扫描指定类型的 peripherals (外围设备),你可以调用  startLeScan(UUID[], BluetoothAdapter.LeScanCallback) 方法,传入一个 app 所支持的 GATT services 的 UUID 数组。下面是一个  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();
           
}
       
});
   
}
};
    注意:在一次扫描过程中,你只能扫描 BLE 设备或典型的 Bluetooth 设备,不能对两者同时进行扫描 。

6. Connecting to a GATT Server
    在首次与 BLE设备进行交互前,首先要做的当然是连接它,更具体的说,是连接设备上 GATT server 。可以通过 connectGatt() 方法来连接一个 BLE 设备上的 GATT server ,该方法有三个参数:一个 Context 对象、autoConnect(是一个 boolean 类型的值,用于指定是否在发现可用的 BLE 设备后立即进行连接)、一个 BluetoothGattCallback 对象的引用,如下
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
    该调用会使 BLE 设备连接到 GATT server,并返回一个 BluetoothGatt 实例,可通过该实例处理 GATT 客户端的操作,调用者(Android app)当然就是 GATT client 了 。BluetoothGattCallback 用于传递相关的结果给 client,如连接状态及其它的 client 操作 。
    在下面的这个例子中,BLE app 通过一个 activity 来连接设备、显示数据、显示设备支持的 GATT services 和 charateristics 。该 activity 会根据用户的操作,与一个名为 BluetoothLeService 的 Service 进行通信,该 service 会通过 Android BLE API 与 BLE 设备进行通信 。
// 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);

   
// Various callback methods defined by the 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
       
// New services discovered
       
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
       
// Result of a characteristic read operation
       
public void onCharacteristicRead(BluetoothGatt gatt,
               
BluetoothGattCharacteristic characteristic,
               
int status) {
           
if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate
(ACTION_DATA_AVAILABLE, characteristic);
           
}
       
}
     
...
   
};
...
}
    注意:这部分所传递进来的参数是由遵循 Bluetooth Heart Rate Measurement  profile specifications 规范的其它模块执行得到的。
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);
}

最终,BroadcastReceiver 会处理好这些事件

// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a
// result of read or notification operations.
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
   
@Override
   
public void onReceive(Context context, Intent intent) {
       
final String 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));
       
}
   
}
};

7. 读取 BLE 属性
    一旦 app 成功地连接到 GATT server 并发现相关的 services,就可以读写相关的 attributes(当然前提是它支持读写操作)。在下面的例子中,我们对 server 端的 services 和 characteristics 进行迭代和显示:
public class DeviceControlActivity extends Activity {
   
...
   
// Demonstrates how to iterate through the supported GATT
   
// Services/Characteristics.
   
// In this sample, we populate the data structure that is bound to the
   
// ExpandableListView on the UI.
   
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);
         
}
   
...
   
}
...
}
8. 接收 GATT 通知
    如下的代码片段演示了当特定的 charateristic 发生改变时,如何给 app 发出通知:
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);
    如果 characteristic 支持 noifications 功能,那么当远程设备的 characteristic 发生改变时,onCharacteristicChanged() 回调方法就会被触发:
@Override
// Characteristic notification
public void onCharacteristicChanged(BluetoothGatt gatt,
       
BluetoothGattCharacteristic characteristic) {
    broadcastUpdate
(ACTION_DATA_AVAILABLE, characteristic);
}    
9. 关闭 Client App
    当 app 不再需要使用 BLE 设备时,就应该调用 close() 以释放相关的资源:
public void close() {
   
if (mBluetoothGatt == null) {
       
return;
   
}
    mBluetoothGatt
.close();
    mBluetoothGatt
= null;
}

你可能感兴趣的:(android,BlueTooth,low,energy,Characteristic)