概述
在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