目前智能家居都被看成是下一个科技爆发点,而智能家居里面使用的技术,响应最高的就算是BLE了,下面,我们说一下android怎么开发BLE,和要注意的一些问题:
1.首先,得知道,android是从android4.3版本才开始支持BLE的,所以,开发的前提就是要知道系统的支持:
if (android.os.Build.VERSION.SDK_INT < 18) {
// 说明sdk不够高版本
}
2.声明权限,BLE,就是低功耗蓝牙的英文缩写,所以,就是要开启蓝牙的权限,在manifest添加:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
3.获取适配器,这里需要注意,和经典蓝牙有个点区别(千万别搞错了):
mBluetoothManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
4.查看手机蓝牙是否开启,如果未开启,则需要主动开启(不推荐),或者提示用户开启(推荐):
if (!mBluetoothAdapter.isEnabled()) {
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 0); // 弹对话框的形式提示用户开启蓝牙
//mBluetoothAdapter.enable(); // 强制开启,不推荐使用
}
// 注册个广播监听这个值,就可以获取到蓝牙的开关状态
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { // 蓝牙开关发生变化
// 这里可以直接使用mBluetoothAdapter.isEnabled()来判断当前蓝牙状态
return;
}
5.接下来就是扫描BLE,这里有2个部分,android4.3-5.0的版本,和5.0+的版本:
// 4.3-5.0版本
mBluetoothAdapter.stopLeScan(lescancallback); // 停止扫描
mBluetoothAdapter.startLeScan(lescancallback); // 开始扫描
private LeScanCallback lescancallback = new LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
// device 就是扫描到的蓝牙对象,里面各种跟蓝牙有关的信息
// rssi信号强度,这个值是个负数,范围一般为0到-100,负数越大,代表信号越弱,一般如果超过-90,连接会出现不理想的情况
// scanRecord广播数据,里面的数据就是蓝牙设备希望手机在连接它之前,让手机知道的信息(稍后的篇章讲解广播数据的组成格式,并且如何解析)
}
};
// 5.0+版本
BluetoothLeScanner scaner = mBluetoothAdapter.getBluetoothLeScanner(); // android5.0把扫描方法单独弄成一个对象了
scaner.stopScan(mScanCallback); // 停止扫描
scaner.startScan(mScanCallback); // 开始扫描
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
// callbackType:确定这个回调是如何触发的
// result:包括4.3版本的蓝牙信息,信号强度rssi,和广播数据scanRecord
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
// 批量回调,一般不推荐使用,使用上面那个会更灵活
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
// 扫描失败,并且失败原因
}
};
因为有2种回调方法,最好在开发的时候,重新打包封装成一个类,根据系统不同,而分别调用不同的方法,因为google会想到推出新方法,那固然会在以后的某个版本把老方法取消。
需要注意的是:
- 在扫描前,最好先调用一次停止扫描
- 而且扫描和停止扫描里面的参数对象必须是同一个,所以,这里不能用匿名的方式来创建扫描回调
- 不要长时间开始扫描,停止扫描,开始扫描,这样的循环,扫描是很耗电的,部分机型这样会导致蓝牙死机
- 当扫描到自己需要的设备时,停止扫描。
6.连接BLE,大部分手机可以通过MAC来直接连接,不需要扫描,小部分一定要先扫描到才可以进行连接(目前所知型号红米1s):
mBluetoothAdapter.stopLeScan(lescancallback); // 停止扫描,连接前,必须停止扫描,不然,失败率很高
device = mBluetoothAdapter.getRemoteDevice(mac); // 通过mac地址获取蓝牙对象,或者可以直接用扫描到的对象,效果都是一样
mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
// BLE连接回调
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, final int status, final int newState) {
super.onConnectionStateChange(gatt, status, newState);
handl.post(new Runnable() {
public void run() {
if (status != BluetoothGatt.GATT_SUCCESS) { // 连接失败判断
return;
}
if (newState == BluetoothProfile.STATE_CONNECTED) { // 连接成功判断
mBluetoothGatt.discoverServices(); // 发现服务
return;
}
if (newState == BluetoothProfile.STATE_DISCONNECTED) { // 连接断开判断
return;
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, final int status) {
super.onServicesDiscovered(gatt, status);
if (status != BluetoothGatt.GATT_SUCCESS) { // 发现服务失败
return;
}
//不是失败的情况就是成功
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, final int status) {
super.onDescriptorWrite(gatt, descriptor, status);
if (status != BluetoothGatt.GATT_SUCCESS) { // 写Descriptor失败
return;
}
//不是失败的情况就是成功
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
//BLE设备主动向手机发送的数据时收到的数据回调
characteristic.getValue(); // 通过这个方法来提取收到的数据
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status != BluetoothGatt.GATT_SUCCESS) { // 写数据失败
return;
}
}
// 还有很多其他回调方法,这里就不一一介绍了
};
需要注意的是:
- BLE连接成功并不意味着可以通信,需要发现服务后,才可以正常通信,不要问我为什么,因为这就是BLE协议
- 以手机开启蓝牙为起始点,对某个设备第一次连接,速度会比较慢,这个速度主要取决于BLE设备的广播频率(不懂广播频率?就问你们固件工程师吧),第二次以后,会快很多,因为手机会缓存很多信息,比如虽然需要重新发现服务,但是实际上,手机并没有去发现服务,而是用了缓存的服务。所以,会比较快
- 中间可能会有回调很多错误,记住一点,不要去深究是什么原因,因为android文档上面没有说,所以,最好的解决方法就是,一旦出现错误,就断开,重新连接,因为BLE底层会对失败进行多次重试,如果告诉你出错误了,也就是说其实底层已经重试了很多次了,还是错误。所以,直接断开,重连就行了
7.获取通信特征值:BLE连接成功后,就需要发现服务,发现服务后,就需要发现特征值,这个特征值才是真正BLE通信需要用到的东西。发现服务后,服务里面有很多BluetoothGattService,这个BluetoothGattService,你可以看做是一个个的文件夹,里面包含了很多文件(特征值BluetoothGattCharacteristic)
private static final String DATA_SERVICE_UUID = "0000f1f0-0000-1000-8000-00805f9b34fb";
BluetoothGattService data_service = mBluetoothGatt.getService(UUID.fromString(DATA_SERVICE_UUID)); // 先获取BluetoothGattService
BluetoothGattCharacteristic txd_charact = data_service.getCharacteristic(UUID.fromString(TXD_CHARACT_UUID)); // 再通过BluetoothGattService获取BluetoothGattCharacteristic特征值
8.手机往BLE设备写入数据:获取到特征值后,就可以正式的进行通信了,手机往设备写数据:
txd_charact.setValue(senddatas); // 往通道写数据
注意:因为大部分BLE只支持最大20byte的通信,所以,如果有大于20byte的数据需要发送,请拆成多个20byte以内的数组,进行分包发送(当然后面我们会讲如何一次性发送超过20byte的方法)
9.BLE设备主动往手机写数据:这个功能叫做notify、indecation(用这种方式的比较少,微信BLE就是用这种方式),汉语叫可通知属性(不是所有的通道都有这个属性),要主动去开启(有些不规范的BLE设备已经默认开启,可以省略这步)。成功与否,需要看回调(看上面第6点)
if (0 != (charact.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY)) { // 查看是否带有可通知属性notify
mBluetoothGatt.setCharacteristicNotification(charact, true);
BluetoothGattDescriptor descriptor = charact.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); // 这包数据什么意思,可以不用管,反正是固定这包数据就是了。
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
} else if (0 != (charact.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE)) { // 查看是否带有indecation属性
mBluetoothGatt.setCharacteristicNotification(charact, true);
BluetoothGattDescriptor descriptor = charact.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
到这里还不够,上面的只是设置BLE设备可以主动向手机发送数据,手机还需要开启是否接收BLE设备发来的数据:这里设置成功与否没有回调,运行了这段代码,就设置成功了
mBluetoothGatt.setCharacteristicNotification(charact, enable); // 设置手机是否接收BLE设备发来的数据
10.断开连接:也有相应的回调(看上面第6点)
mBluetoothGatt.disconnect();
需要注意的是:android的BLE有很多问题,如果一定时间(一般5秒,根据BLE设备不同而不同)内没有回调断开,则需要强制关一下手机蓝牙,至于是强制开是强制开,还是用提示用户的方式,根据自己的需求而定