概述
在Android 4.3 (API level 18)中,Android为我们提供了低功耗蓝牙的支持,最近很火的可穿戴设备的主要通信方式就是低功耗蓝牙。与传统蓝牙相比,其最主要的目的就是更低的功耗用于支持用户使用可穿戴设备,本篇博客主要介绍关于其的基础。
相关术语
Attribute Protocol (ATT):全称 attribute protocol,中文名“属性协议”。它是 BLE 通信的基础。ATT 把数据封装,向外暴露为“属性”,提供“属性”的为服务端,获取“属性”的为客户端。ATT 是专门为低功耗蓝牙设计的,结构非常简单,数据长度很短。
Generic Attribute Profile (GATT):全称 Generic Attribute Profile, 中文名“通用属性配置文件”。它是在ATT 的基础上,对 ATT 进行的进一步逻辑封装,定义数据的交互方式和含义。GATT是我们做 BLE 开发的时候直接接触的概念。
有关上面两个协议可以参看:GATT Profile 简介
Characteristic: 它定义了数值和操作,包含一个Characteristic声明、Characteristic属性、值、值的描述(Optional)。通常我们讲的 BLE 通信,其实就是对 Characteristic 的读写或者订阅通知。比如在实际操作过程中,我对某一个Characteristic进行读,就是获取这个Characteristic的value。
Descriptor:用于描述Characteristic的value。
Service:Service是一系列的Characteristic的集合。Service 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。
基于GATT连接的方式
外设通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。这里有且仅有两个角色,发起连接的一方,叫做中心设备—Central,被连接的设备,叫做外设—Peripheral。
- 外围设备:这一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更加相对强大的中心设备,例如小米手环。
- 中心设备:中心设备相对比较强大,用来连接其他外围设备,例如手机等。
GATT 连接需要特别注意的是:GATT 连接是独占的。也就是一个 BLE 外设同时只能被一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。当设备断开,它又开始广播。中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。
GATT 通信的双方是 C/S 关系。外设作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的定义。中心设备是 GATT 客户端(Client),它向 Server 发起请求。需要注意的是,所有的通信事件,都是由客户端发起,并且接收服务端的响应。
基础使用
定义权限:
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果需要扫描BLE设备。那么还需要拥有定位权限。注:6.0以上需要动态申请定位权限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> 或者 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
检查设备是否支持BLE设备(并不是检查是否支持蓝牙)
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
BluetoothAdapter : 系统蓝牙适配器,通过这个适配器,我们可以使用系统蓝牙
private BluetoothAdapter mBluetoothAdapter; ... // Initializes Bluetooth adapter. final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter();
我们还可以使用BluetoothAdapter的静态方法
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
常用方法
//bluetoothAdapter为null,说明设备不支持蓝牙 BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); //蓝牙是否可用 bluetoothAdapter.isEnabled(); //开启蓝牙,不推荐使用,因为无法知道用户是否拒绝 bluetoothAdapter.enable(); //关闭蓝牙 bluetoothAdapter.disable();
开启蓝牙:通过下面这种方法,设备会弹出选择框让用户选择是否开启蓝牙,然后我们在onActivityResult回调中即可获知是否开启。
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
蓝牙广播
我们也可以监听BluetoothAdapter.ACTION_STATE_CHANGED广播,可以获取到蓝牙状态改变。
IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); registerReceiver(mBroadcastReceiver, filter);
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//当前状态
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
//上一状态
int statePre = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1);
String msg = null;
switch (state) {
case BluetoothAdapter.STATE_TURNING_ON:
msg = "turning on";
break;
case BluetoothAdapter.STATE_ON:
msg = "on";
break;
case BluetoothAdapter.STATE_TURNING_OFF:
msg = "turning off";
break;
case BluetoothAdapter.STATE_OFF:
msg = "off";
break;
}
Log.i(TAG, "onReceive: " + msg);
}
};
扫描BLE设备
我们可以使用adapter.startLeScan来进行蓝牙扫描,由于扫描蓝牙操作很耗电,所以当我们扫描到需要的设备时,停止扫描,扫描时设置一个超时时间。
/**
* 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);
}
...
}
...
}
private LeDeviceListAdapter mLeDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
}
};
说明:上面的回调中,rssi为信号强度指示,最大为0,一般都是负数,比如-50比-70信号要强。
连接BLE设备
在上面扫描的回调中我们能获取到BluetoothDevice,然后我们使用列表展示给用户,当用户选择时,使用下面的方法进行连接,主要关注第三个回调参数。因为我们的主要操作均在回调中完成。
//获取蓝牙名 device.getName(); //获取硬件名 device.getAddress(); //获取绑定状态 device.getBondState(); mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
注意:connectGatt方法有一个重载,可以设置通道,所以需要特殊判断版本,参看:FastBle、stackoverflow
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return device.connectGatt(context, autoConnect, bluetoothGattCallback, TRANSPORT_LE);
} else {
return device.connectGatt(context, autoConnect, bluetoothGattCallback);
}
在下面的回调中我们就能获取到BluetoothGatt对象,这个对象则表示蓝牙设备之间的"连接",我们可以通过gatt.writeCharacteristic()或gatt.readCharacteristic()等方法去进行通信。
public BluetoothGattCallback getCallback() {
return new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
//已连接
if (newState == BluetoothProfile.STATE_CONNECTED) {
//启动发现服务
gatt.discoverServices();
}
//断开连接
else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
}
}
//服务被发现
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
}
//Characteristic读取回调
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
//Characteristic改变回调
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
};
}
获取GATT notifications
对于我们的app来说,由设备主动通知app信息发生改变是必须的,我们可以使用setCharacteristicNotification来打开Characteristic的通知,并且将描述设置为打开。代码如下
private BluetoothGatt mBluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
...
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
...
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(descriptor_id));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
当一个通知到来的时候,onCharacteristicChanged会被回调。
关闭连接
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
参考链接:
老哥666