概述
- 中心角色和外围角色
在BLE中存在两个角色,一个是中心角色(Central),一个是外围角色(Peripheral),蓝牙设备或手机都可以单独作为Central或Peripheral角色。外设角色的作用是为中心角色提供各种数据,中心角色可以扫描并接收多个外设角色数据( 外围角色中的设备进行广播,中心角色的设备扫描寻找广播),数据以服务(Service)和特征(Characteristic)的形式呈现。其中Ble中心角色的API在Android 4.3
得到支持,而外围角色的API在Android 5.0
才得到支持 - 协议、服务、特征、描述符
一份协议(BluetoothGatt)由一个或多个服务(BluetoothGattService)构成,一个服务由零个或多个特征(BluetoothGattCharacteristic)构成,一个特征可以包含零个或多个描述符(BluetoothGattDescriptor)。每一个服务、特征、描述符都有一个UUID作为唯一识别符,识别符有通用的,也可以自定义,也可以随机生成,固定格式00000000-0000-0000-0000-000000000000(8-4-4-4-12),一般来说自定义的UUID只有前8位有变化,后面的基本是固定的0000-1000-8000-00805f9b34fb,所以一个自定义的UUID一般看起来就像这样 “0000????-0000-1000-8000-00805f9b34fb” 通配符就表示4个16进制数。每一个特征都有其属性和权限(Read | Write | Notify | Indicate),特征根据属性可读可写。在每个Ble蓝牙设备中,都会有两个默认的服务如下:
//Generic Access(Generic Attribute Profile 通用属性规范GATT)
service:00001801-0000-1000-8000-00805f9b34fb
characteristic:00002a05-0000-1000-8000-00805f9b34fb
//Generic Attribute (Generic Access Profile 通用接入规范GAP)
service:00001800-0000-1000-8000-00805f9b34fb
characteristic:00002a00-0000-1000-8000-00805f9b34fb
characteristic:00002a01-0000-1000-8000-00805f9b34fb
characteristic:00002aa6-0000-1000-8000-00805f9b34fb
- 适配器,扫描器:
每一台支持蓝牙的手机中都会有一个蓝牙适配器,由蓝牙管理器管理着,从其中获得蓝牙适配器。适配器中自带扫描器,使用扫描器可以扫描周边的蓝牙设备。
Ble中常用类
- BluetoothDeivce:蓝牙设备,代表一个具体的蓝牙外设
- BluetoothManager:蓝牙管理器,主要用于获取蓝牙适配器和管理所有和蓝牙相关的东西
- BluetoothAdapter:蓝牙适配器,每一台支持蓝牙功能的手机都有一个蓝牙适配器,一般来说,只有一个,可以通过BluetoothManager获取
- BluetoothLeScanner:蓝牙适配器里面的扫描器,用于扫描BLE外设,可以通过BluetoothAdapter获取
- BluetoothGatt:通用属性协议,定义了BLE通讯的基本规则,就是通过把数据包装成服务和特征的约定过程,可以通过建立连接获取
- BluetoothGattService:服务,描述了一个BLE设备的一项基本功能,由零或多个特征组构成,可以通过BluetoothGatt获取并自定义(现有的约定服务可以在bluetooth.org上找到)
- BluetoothGattCallback:作为中央的回调类,用于回调GATT通信的各种状态和结果
- BluetoothGattServerCallback:作为周边的回调类,用于回调GATT通信的各种状态和结果
- BluetoothGattCharacteristic:特征,里面包含了一组或多组数据,是GATT通信中的最小数据单元。
- BluetoothGattDescriptor:特征描述符,对特征的额外描述,包括但不仅限于特征的单位,属性等。
- BluetoothProfile:一个通用的规范,按照这个规范来收发数据
使用流程
中心设备
:判断蓝牙是否可用->打开蓝牙->开始扫描->获取被扫描到的设备->连接设备->发现服务->获取到指定特征->写入特征值
外围设备
:判断蓝牙是否可用->打开蓝牙->创建广播数据->发送广播->添加服务至广播->根据监听获取写入的数据
下图是中心设备的使用流程图 来源
权限声明
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
//获取扫描结果权限,Android6.0以上需要动态权限(这两个权限是在经典蓝牙的扫描中需要声明的)
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
//或者
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
//当android:required为true的时候,app只能强制运行在支持BLE功能的设备商,为false的时候,可以运行在所有设备上,但某些方法需要手动检测,否则可能存在隐性BUG
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
Ble可用性判断
不是任何设备都支持BLE,最开始要确定设备是否支持,还要确定蓝牙已经打开。
- Android系统从4.3以后才支持,这是先决条件
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
... ...
}
- 判断蓝牙是否可用
//小于等于API17时直接使用BluetoothAdapter.getDefaultAdapter()来获取Adapter
private BluetoothAdapter getAdapter(){
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
} else {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
return mBluetoothAdapter;
}
if(mBluetoothAdapter == null){
//蓝牙不可用
}
//Ble不可用
private boolean checkIfSupportBle(){
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
蓝牙开启
//开启蓝牙功能需要一小段时间,所以不能执行开启蓝牙立即执行其他操作,这时蓝牙实际还没有开启,回出现异常,所以后续操作应该在蓝牙状态广播中处理
private void enableBluetooth(){
if (mBluetoothAdapter == null && !mBluetoothAdapter.isEnabled()) {
//使用系统弹框来启动蓝牙,REQUEST_ENABLE_BT为自定义的开启蓝牙请求码
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
//或者静默打开
bluetoothAdapter.enable();
}
}
//静默关闭
bluetoothAdapter.disable();
蓝牙开关广播 更多蓝牙相关广播
//广播的Action
BluetoothAdapter.ACTION_STATE_CHANGED
//四种状态值
BluetoothAdapter.STATE_ON //已开启
BluetoothAdapter.STATE_OFF //已关闭
BluetoothAdapter.STATE_TURNING_ON //正在开启
BluetoothAdapter.STATE_TURNING_OFF //正在关闭
//当前状态值的获取
int curState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
扫描具体使用流程
- 经典通用方式
//此过程大概持续10秒,当扫描到蓝牙设备后,会发出广播,只要在需要的地方注册接收广播,就可以获得扫描结果。这种方法可以扫描出所有蓝牙设备,包括BLE,但貌似不同手机有不同体验。
private void startDiscover(){
mBluetoothAdapter.startDiscover();
}
//注册此广播,监听BluetoothDevice.ACTION_FOUND,以接收系统消息取得扫描结果
private class DeviceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(BluetoothDevice.ACTION_FOUND.equals(action)){
//这个就是所获得的蓝牙设备。
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
mDevices.add(device );
}
}
}
- Ble扫描方式
//Ble蓝牙扫描方式在5.0之后发生了变更
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
//过滤器设置
ArrayList<ScanFilter> filters = new ArrayList<>();
ScanFilter.Builder filterBuilder = new ScanFilter.Builder();
filters.add(filterBuilder);
//扫描结果回调设置
ScanCallback scanCallback = new ScanCallback(){
public void onScanResult(int callbackType, ScanResult result){
//scanResult.getDevice()取得device
}
};
//扫描参数设置
ScanSettings.Builder settingsBuilder = new ScanSettings.Builder();
//这种扫描方式占用资源比较高,建议当应用处于前台时使用该模式
settingsBuilder.setScanMode(SCAN_MODE_LOW_LATENCY);
// 开启扫描,第三项参数见下面,该操作为异步操作的,但是会消耗大量资源,一般扫描时长为12秒,建议找到需要的设备后,执行取消扫描
bluetoothLeScanner.startScan(scanCallback, filter, settingsBuilder.build());
}else{
//扫描结果回调设置
BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback(){
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord){}
};
//该方法已被声明为过时方法
adapter.startLeScan(leScanCallback)
}
- 停止扫描
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
bluetoothLeScanner.stopScan(mScanCallback);
}else{
bluetoothAdapter.stopLeScan(mLeScanCallback);
}
设备信息获取
- 根据蓝牙地址获取蓝牙设备
BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
- 已绑定设备获取
private void getBoundDevices(){
Set<BluetoothDevice> boundDevices = mBluetoothAdapter.getBondedDevices();
for(BluetoothDevice device : boundDevices){
//对device进行其他操作,比如连接等。
}
}
- 设备详细信息获取
private void showDetailOfDevice(){
//获得设备名称,多个设备可以有同一个名称。
String deviceName = bluetoothDevice.getName();
//获取设备物理地址,一个设备只能有一个物理地址,每个设备都有每个设备的物理地址,无法相同。
String deviceMacAddress =bluetoothDevice.getAddress();
//绑定设备
bluetoothDevice.createBond();
}
连接
- 建立连接
//扫描回调的BluetoothDevice用于建立GATT连接,参数2是控制是否自动链接,为true的时候,当设备进入中心范围,会进行自动连接,为false时是主动连接,一般推荐使用主动连接,因为这样连接速度更快。
BluetoothGatt bluetoothGatt = bluetoothDevice.connectGatt(context, false, bluetoothGattCallback);
//如果已经连接后,但是又断开了连接,需要重新连接时
bluetoothGatt.connect();
- 连接状态的获取
//连接状态改变会在BluetoothGattCallback的onConnectionStateChange方法中回调,同时也可以随时主动去查询
int state = bluetoothManager.getConnectionState(bluetoothDevice, BluetoothGatt.GATT);
- 发现服务
//在建立连接成功后进行发现服务,onServicesDiscovered为发现服务的回调
bluetoothGatt.discoverServer();
- 特征改变
//该方法一般是在发现服务后,进行设置的,设置该方法的目的是让硬件在特征数据改变的时候,发送数据给app,app则通过onCharacteristicChanged方法回调给用户,从参数中可获取到回调回来的数据
bluetoothGatt.setCharacteristicNotification(characteristic, true);
- 连接数据回调(
在非UI线程
)
//mBluetoothGattCallback 为所有蓝牙数据回调的处理者,也是整个蓝牙操作当中最为核心的一部分。它里面有很多方法,但并非所有都需要在开发当中用到,这里列出来只是作为部分解析,需要哪个方法,就重写哪个方法,不需要的,直接去掉
private BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
//当设备与中心连接状态发生改变时,下面是已连接的判断
if (status == BluetoothGatt.GATT_SUCCESS && newState== BluetoothProfile.STATE_CONNECTED){
...
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
//当发现设备服务时,会回调到此处,下面是遍历所有发现的服务
if (status == BluetoothGatt.GATT_SUCCESS) {
//gatt.getServices()可以获得外设的所有服务
for (BluetoothGattService service : gatt.getServices()) {
//每发现一个服务,我们再次遍历服务当中所包含的特征,service.getCharacteristics()可以获得当前服务所包含的所有特征
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
//通常可以把所发现的特征放进一个列表当中以便后续操作,如果你想知道每个特征都包含哪些描述符,很简单,再用一个循环去遍历每一个特征的getDescriptor()方法。
mCharacteristics.add(characteristic);
//UUID获取,或者根据UUID判断是否是自己需要的服务/特征
Log.i("", characteristic.getUuid().toString());//打印特征的UUID。
}
}
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//读取特征后回调到此处。
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//写入特征后回调到此处,status == BluetoothGatt.GATT_SUCCESS代表写入成功
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
//当特征(值)发生变法时回调到此处。
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
//读取描述符后回调到此处。
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
//写入描述符后回调到此处
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
//暂时没有用过。
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
//Rssi表示设备与中心的信号强度,发生变化时回调到此处。
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
//暂时没有用过。
}
};
数据传输
- 写入特征值,会回调onCharacteristicWrite
BluetoothGattService service = bluetoothGatt.getService(SERVICE_UUID);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHARACTERISTIC_UUID);
characteristic.setValue(writeString);
//设置回复方式
bluetoothGattCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
bluetoothGatt.writeCharacteristic(characteristic);
- 写入描述符
bluetoothGattDescriptor.setValue(writeContent.getBytes());
bluetoothGatt.writeDescriptor(bluetoothGattDescriptor);
- 读取特征值
bluetoothGatt.readCharacteristic(characteristic);
外围设备实现(被连接者/服务端 )
- 广播的设定
private AdvertiseSettings buildAdvertiseSettings() {
AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder()
//设置广播模式:低功耗、平衡、低延时,广播间隔时间依次越来越短
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
//设置是否可以连接,一般不可连接广播应用在iBeacon设备上
.setConnectable(true)
//设置广播的最长时间,最大时长为LIMITED_ADVERTISING_MAX_MILLIS(180秒)
.setTimeout(10*1000)
//设置广播的信号强度 ADVERTISE_TX_POWER_ULTRA_LOW, ADVERTISE_TX_POWER_LOW,
//ADVERTISE_TX_POWER_MEDIUM, ADVERTISE_TX_POWER_HIGH 信号强度依次增强
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
return builder.build();
}
- 对外广播的数据(数据限制 31 Bytes)
private AdvertiseData buildAdvertiseData() {
AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
//添加厂家信息,第一个参数为厂家ID(不足两个字节会自动补0,例如这里为0x34,实际数据则为34,00)
//一般情况下无需设置,否则会出现无法被其他设备扫描到的情况
.addManufacturerData(0x34, new byte[]{0x56})
//添加服务进行广播,即对外广播本设备拥有的服务
.addServiceData(...);
//是否广播信号强度
.setIncludeTxPowerLevel(true)
//是否广播设备名称
.setIncludeDeviceName(true);
return dataBuilder.build();
}
- 对外广播(即允许被扫描到)
private void advertise() {
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
BluetoothAdapter mAdapter = bluetoothManager.getAdapter();
AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
//广播成功,建议在这里开启服务
}
@Override
public void onStartFailure(int errorCode) {
//广播失败
}
};
BluetoothLeAdvertiser mAdvertiser = mAdapter.getBluetoothLeAdvertiser();
mAdvertiser.startAdvertising(buildAdvertiseSettings(), buildAdvertiseData(), mAdvertiseCallback);
}
- 开启服务
//声明需要广播的服务的UUID和特征的UUID,注意不要占用蓝牙设备默认的UUID
UUID UUID_SERVICE = UUID.fromString("00001354-0000-1000-8000-00805f9b34fb");
UUID UUID_CHARACTERISTIC = UUID.fromString("00001355-0000-1000-8000-00805f9b34fb");
UUID UUID_DESCRIPTOR = UUID.fromString("00001356-0000-1000-8000-00805f9b34fb");
//外围设备状态、数据回调,详情见后面
BluetoothGattServerCallback serverCallback = new BluetoothGattServerCallback() {...};
//GATT协议服务
BluetoothGattServer server = bluetoothManager.openGattServer(this, serverCallback);
//创建一个服务
BluetoothGattService service = new BluetoothGattService(UUID_SERVICE,
BluetoothGattService.SERVICE_TYPE_PRIMARY);
//创建一个特征,首先此characteristic属性满足BluetoothGattCharacteristic.PROPERTY_WRITY或BluetoothGattCharacteristic.PROPERTY_WRITY_NO_RESPONSE,
//如果其property都不包含这两个,写特征值writeCharacteristic()函数直接返回false,什么都不做处理。其次此characteristic权限应满足
//BluetoothGattCharacteristic.PERMISSION_WRITE,否则onCharacteristicWrite()回调收到GATT_WRITE_NOT_PERMITTED回应
//如果需要既能读,又能写,则可以参考如下写法
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(Constants.UUID_CHARACTERISTIC,
BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
//创建一个描述符
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,
BluetoothGattDescriptor.PERMISSION_READ);
//将描述符添加到特征中,一个特征可以包含0至多个描述符
characteristic.addDescriptor(descriptor);
//将特征添加到服务中,一个服务可以包含1到多个特征
service.addCharacteristic(characteristic);
server.addService(service);
- BluetoothGattServerCallback
public abstract class BluetoothGattServerCallback {
public BluetoothGattServerCallback() {
}
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
//连接状态被改变
}
public void onServiceAdded(int status, BluetoothGattService service) {
//添加服务
}
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
//被读取特征
}
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
//被写入数据,其中device是写入的设备,value是写入的值,responseNeeded指是否需要恢复,如果需要恢复则调用gattServer.sendResponse()方法回复
}
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
//被读取描述符
}
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
//被写入描述符
}
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
}
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
}
@Override
public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
}
@Override
public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
}
}
-
广播数据格式
广播数据(或者扫描应答数据)由一个一个的AD Structure组成,对于未满31bytes的其它数据,则填充为0;每个AD Structure由两部分组成:1byte的长度信息(Data的长度),和剩余的Data信息;
Data信息又由两部分组成:AD Type(长度不定)指示该AD Structure的类型,以及具体的AD Data。
例如:
02 01 06 03 03 aa fe 17 16 aa fe 00 -10 00 01 02 03 04 05 06 07 08 09 0a 0b 0e 0f 00 00 00 00
02 01 06
是一个AD Structure:Data的长度是02;Data是01 06;AD Type是01(Flags);AD Data是06,表明支持General Discoverable Mode、不支持BR/EDR。
03 03 aa fe
是一个AD Structure:Data的长度是03;Data是03 aa fe;AD Type是03(16 bits的Service UUID);AD Data是aa fe,是Eddystone profile的Service UUID。AD Type查询
注意
- 外围设备广播的数据最多31 byte
- 写特征一次最多写入20 byte
- 单次扫描时间不宜过长(建议10~15秒)
- BluetoothGattCallback的回调是在非UI线程
- 每个设备所能使用的Gatt连接是有限个数的,所以应该断开连接时也关闭Gatt
bluetoothGatt.close()
,然后重新连接 - 外围设备蓝牙关闭后,中心设备的BluetoothGattCallback的onConnectionStateChange会回调状态码
status = 19
,newState = 0
(断开连接) - 外围设备蓝牙关闭后,中心设备再进行连接,BluetoothGattCallback的onConnectionStateChange会回调状态码
133
,并且当外围设备蓝牙开启后再调用中心设备的bluetoothGatt.connect()不能重新连接,仍然是133错误,所以建议针对133错误进行关闭gatt并释放资源