1、简介
BLE是Bluetooth Low Energy的缩写,又叫蓝牙4.0,区别于蓝牙3.0和之前的技术
主要特点是快速搜索,快速连接,超低功耗保持连接和数据传输,缺点:数据传输速率低,由于其具有低功耗特点,所以经常用在可穿戴设备之中
BLE设备分单模和双模两种,双模简称BR,商标为Bluetooth Smart Ready,单模简称BLE或者LE,商标为Bluetooth Smart,双模兼容传统蓝牙,可以和传统蓝牙通信,也可以和BLE通信,单模只能和BR和单模的设备通信,不能和传统蓝牙通信,由于功耗低,待机长,所以常用在手环的智能设备上
Android是在4.3后才支持BLE,这说明不是所有蓝牙手机都支持BLE,而且支持BLE的蓝牙手机一般是双模的
2、连接流程
- 1、获取BluetoothManager对象或者是通过BluetoothAdapter的对象,两个差多不
(BluetoothManager) getContext().getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter adapter = getBluetoothManager().getAdapter();
BluetoothAdapter.getDefaultAdapter()
蓝牙是夸进程通信的,所以源码使用了aidl来解决跨进程通信
public static synchronized BluetoothAdapter getDefaultAdapter() {
if (sAdapter == null) {
IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);
if (b != null) {
IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);
sAdapter = new BluetoothAdapter(managerService);
} else {
Log.e(TAG, "Bluetooth binder is null");
}
}
return sAdapter;
}
- 2、开始扫描BLE设备,两种方式
1.使用 adapter.startLeScan(mScanCallback) 这个方法不建议使用,回调在ui线程,不要做耗时操作
adapter.startLeScan(mScanCallback)
private final BluetoothAdapter.LeScanCallback mScanCallback = new BluetoothAdapter.LeScanCallback() {
/**
* ui线程,不要做耗时操作
* @param device
* @param rssi 信号强度
* @param scanRecord 广播数据,解析广播
*/
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
ScanResult result = new ScanResult(device, rssi, scanRecord);
if (TextUtils.isEmpty(device.getName())){
}else if (!mScanResults.containsKey(device.getAddress())) {
mScanResults.put(device.getAddress(), result);
Log.v(TAG, String.format("onLeScan: mac = %s, name = %s, record = (%s)%d",
device.getAddress(), device.getName(), byteToString(scanRecord), scanRecord.length));
mAdapter.refresh(mScanResults);
}
}
};
2、通过BluetoothLeScanner,目前项目使用这个
BluetoothAdapter adapter = getBluetoothManager().getAdapter();
BluetoothLeScanner bluetoothLeScanner = adapter.getBluetoothLeScanner();
bluetoothLeScanner.startScan(new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
});
3、 BluetoothAdapter.LeScanCallback回调,获取到了 BluetoothDevice 了,然后在获取到GATT句柄
4、获取到GATT句柄 BluetoothGatt,回调都在binder线程池,不能刷新ui,回调在哪个线程都不确定,建议所有的回调post到统一的线程,因为多线程同步有点问题哦,难搞的很
mBluetoothGatt = mDevice.connectGatt(this, false, new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.v(TAG, String.format("onConnectionStateChange: status = %d, newState = %d", status, newState));
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) {
onConnected();
if (!mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)) {
disconnect();
}
if (!mBluetoothGatt.discoverServices()) {
disconnect();
}
} else {
disconnect();
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.v(TAG, String.format("onServicesDiscovered: status = %d", status));
if (status == BluetoothGatt.GATT_SUCCESS) {
refreshProfile();
startChannel();
BluetoothUtils.setCharacteristicNotification(gatt, UUID_MYSERVICE, UUID_PACKET, true);
mBtnPacket.postDelayed(new Runnable() {
@Override
public void run() {
mBtnPacket.setEnabled(true);
}
}, 1000);
} else {
disconnect();
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.v(TAG, String.format("onCharacteristicRead: service = %s, character = %s, value = %s, status = %d",
characteristic.getService().getUuid(),
characteristic.getUuid(),
ByteUtils.byteToString(characteristic.getValue()),
status));
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.v(TAG, String.format("onCharacteristicWrite service = %s, character = %s, status = %d",
characteristic.getService().getUuid(),
characteristic.getUuid(),
status));
if (mChannelCallback != null) {
mChannelCallback.onCallback(status == BluetoothGatt.GATT_SUCCESS ? Code.SUCCESS : Code.FAIL);
mChannelCallback = null;
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.v(TAG, String.format("onCharacteristicChanged service = %s, character = %s, value = %s",
characteristic.getService().getUuid(),
characteristic.getUuid(),
ByteUtils.byteToString(characteristic.getValue())));
mChannel.onRead(characteristic.getValue());
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
Log.v(TAG, String.format("onReadRemoteRssi rssi = %d, status = %d", rssi, status));
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
Log.v(TAG, String.format("onDescriptorWrite service = %s, character = %s, descriptor = %s, status = %d",
descriptor.getCharacteristic().getService().getUuid(),
descriptor.getCharacteristic().getUuid(),
descriptor.getUuid(), status));
}
});
-
log可以看出这里是在binder线程池,每次连接都是一个独立的对象,也在一个独立的线程中,所以每次断开连接都要把GATT关闭掉,而不是 disconnect();
- 通过gatt= mDevice.connectGatt(this, false, new BluetoothGattCallback()出来的对象,其实是和
public void onServicesDiscovered(BluetoothGatt gatt, int status)
是同一个对象,这个很有意思?具体我也不清楚为啥
- 注意事项
- GATT重连,BluetoothGatt 调用connet函数,但是不建议这样做,而是重新开启一个gatt, 而不是用旧的gatt重连,mBluetoothGatt.connect(); 就是不用使用内部的方法区进行重连?why?因为这里面有好多的坑
- 断开连接 mBluetoothGatt.close(); 或者使用 disconnect();其实最好是使用close这个方法,这是稳定性最好的办法, disconnect(); 有些很奇怪的问题,能力不够,不要去采坑,close后,就不收到回调了,不用等待回调
- BluetoothGattCallback接口回调:onCharacteristicChanged 回调中读取蓝牙设备给我们发送的数据
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.v(TAG, String.format("onCharacteristicChanged service = %s, character = %s, value = %s",
characteristic.getService().getUuid(),
characteristic.getUuid(),
ByteUtils.byteToString(characteristic.getValue())));
mChannel.onRead(characteristic.getValue());
}
- 通过 BluetoothGatt 类并且通过uuid获取到 BluetoothGattService ,在获取到BluetoothGattCharacteristic ,通过这个对象去发送数据, UUID 后面解释
private BluetoothGattCharacteristic getNotifyCharacteristic() {
if (mBluetoothGatt != null) {
BluetoothGattService service = mBluetoothGatt.getService(UUID_MYSERVICE);
return service != null ? service.getCharacteristic(UUID_PACKET) : null;
}
return null;
}
BluetoothGattCharacteristic characteristic = getNotifyCharacteristic();
characteristic.setValue(bytes);
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
mBluetoothGatt.writeCharacteristic(characteristic);
mChannelCallback = callback;
3、什么是GATT
简介:
GATT全称Generic Attribute Profile,中文名叫通用属性协议,它定义了services和characteristic两种东西来完成低功耗蓝牙设备之间的数据传输,它是建立在通用数据协议Attribute Protocol (ATT),之上的,ATT把services和characteristic以及相关的数据保存在一张简单的查找表中,该表使用16-bit的id作为索引
一旦两个设备建立了连接,GATT就开始发挥作用
GATT连接是独占的,也就意味着一个BLE周边设备同时只能与一个中心设备连接,一旦周边设备与中心设备连接成功,直至连接断开,它不再对外广播自己的存在,其他的设备就无法发现该周边设备的存在了。
周边设备和中心设备要完成双方的通信只能通过建立GATT连接的方式。
-
GATT架构图
1、profile可以理解为一种规范,一个标准的通信协议,其存在于手机中,蓝牙组织规定一些标准的profile:HID OVER GATT ,防丢器等,每个profile中包含了多个service了。蓝牙规范(Profile)是指蓝牙通信在那一种用途下应该使用的通信协议和相关的规范。蓝牙1.1定义的profile有13个。SIG认为蓝牙设备有4个最基本的Profile
2、service可以理解为一个服务,在ble从机中,通过有多个服务,例如电量信息服务、系统信息服务等,每个service中又包含多个characteristic特征值。每个具体的characteristic特征值才是ble通信的主题。比如当前的电量是80%,所以会通过电量的characteristic特征值存在从机的profile里,这样主机就可以通过这个characteristic来读取80%这个数据
3、characteristic特征值,ble主从机的通信均是通过characteristic来实现,可以理解为一个标签,通过这个标签可以获取或者写入想要的内容。
把Profile 当做是国家,Service 就是中国足球。Characteristic就是国足的某一个人,我需要找到这个人告诉他,明天不要提假球(类似对ble设备设置信息),所以需要uuid,uuid一般是问询问硬件工程师,问他要这个设备的UUID
- 主要用的是service UUID 和characteristic UUID一般读,写和通知的UUID 就是 characteristic UUID
4、UUID到底是个啥?
- 通用唯一识别码(英语:Universally Unique Identifier,缩写:UUID)是用于计算机体系中以识别信息数目的一个128位标识符,还有相关的术语:全局唯一标识符
- UUID是由一组32位数的16进制数字所构成,故UUID理论上的总数为1632=2128,约等于3.4 x 1038。也就是说若每纳秒(ns)产生1兆个UUID,要花100亿年才会将所有UUID用完
- UUID 的 16 个 8 位字节表示为 32 个十六进制(基数16)数字,显示在由连字符分隔 '-' 的五个组中,"8-4-4-4-12" 总共 36 个字符(32 个字母数字字符和 4 个连字符)。例如:123e4567-e89b-12d3-a456-426655440000
5、BLE一些问题的总结,刚入坑不久,持续记录
- 经典蓝牙是通过socket ,BLE是通过gatt
- BLE分Service /Characteristic 。一个地址,Service相当于省,Characteristic相当于市
- 我觉得未来物联网发展到繁华的时候:最靠谱的设备识别方式是解析广播内容,其中有个设备id,然后拿着这个id,去云端查询设备信息,然后配对。但是我目前是靠名字配对的
- GATT 句柄泄漏,不用了就把GATTclose掉,不关的话,就会重新开GATT,导致系统句柄耗尽,导致没有办法创建新的连接
- 重复连接,打开一个gatt句柄,就会创建一个通道,这个通道是独立的
- 在高版本的手机上,在发现新的设备的过程,还需要运行时的权限,切记 (Android M 以上)
- gatt的回调是binder线程池,如何把结果的数据统一的管理,这个问题我想的解决方式动态代理去放到统一的线程,所以我现在在做这样的工作
- and so on