第1章:APP端蓝牙扫描与连接
大家好,我是你们的老朋友。今天咱们聊聊美容仪APP开发中最基础、也最容易踩坑的一环——蓝牙扫描与连接。
说实话,我刚开始做蓝牙开发那会儿,也被iOS和Android的差异折腾得够呛。明明同一个设备,iOS上搜不到,Android上连不上,这种问题我遇到过不下十次。今天就把这些经验掰开揉碎了讲给你听。
1.1 BLE扫描:iOS和Android的差异
先说说扫描。BLE扫描,说白了就是让手机去找周围有哪些蓝牙设备在“喊话”。
iOS平台:
- iOS的扫描由系统统一管理,你只需要调用
CBCentralManager的scanForPeripherals方法 - 有个坑:iOS不允许扫描时指定特定的Service UUID以外的过滤条件。我当年想按设备名过滤,结果发现根本不行
- 扫描回调是异步的,每次发现设备都会调用
didDiscover代理方法
// iOS 扫描示例
centralManager.scanForPeripherals(withServices: [serviceUUID], options: nil)
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// 处理发现的设备
print("发现设备: \(peripheral.name ?? "未知")")
}
Android平台:
- Android 5.0+ 使用
BluetoothLeScanner进行扫描 - 可以设置更灵活的过滤条件,比如设备名、MAC地址、Service UUID
- 注意:Android 6.0+ 需要动态申请位置权限,否则扫描不到任何设备。这个我吃过亏,用户反馈说“你们的APP怎么搜不到设备”,结果发现是权限没给
// Android 扫描示例
BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner();
ScanFilter filter = new ScanFilter.Builder()
.setServiceUuid(new ParcelUuid(serviceUUID))
.build();
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
scanner.startScan(Arrays.asList(filter), settings, scanCallback);
核心差异总结:
- iOS:系统级管理,回调简单,但灵活性差
- Android:开发者控制力强,但权限和兼容性问题多
1.2 连接与断开:稳定是第一要务
扫描到设备后,下一步就是连接。嗯,这里要注意,连接不是一次就能成功的。
iOS连接流程:
- 调用
centralManager.connect(peripheral, options: nil) - 等待
didConnect回调 - 连接成功后,需要调用
discoverServices发现服务 - 再调用
discoverCharacteristics发现特征
// iOS 连接与发现服务
centralManager.connect(peripheral, options: nil)
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("连接成功")
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
// 发现服务后,再发现特征
for service in peripheral.services! {
peripheral.discoverCharacteristics([charUUID], for: service)
}
}
Android连接流程:
- 调用
bluetoothDevice.connectGatt(context, false, gattCallback) - 在
onConnectionStateChange中判断连接状态 - 连接成功后,系统会自动调用
onServicesDiscovered
// Android 连接与发现服务
BluetoothGatt gatt = device.connectGatt(context, false, new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
// 服务发现成功,可以操作特征了
}
});
常见连接失败问题:
- iOS 133错误:设备连接超时。我遇到过,通常是设备端蓝牙模块没准备好
- Android 133错误:GATT连接失败。多半是设备端异常断开或信号太弱
- 连接后立即断开:检查是否在连接过程中调用了
disconnect或close
1.3 重连逻辑:让用户体验更丝滑
美容仪这种设备,用户可能用着用着就走远了,或者设备没电了。重连机制必须做好。
我个人习惯的做法是:
- 维护一个“已配对设备”列表,保存设备的UUID或MAC地址
- 当连接断开时,自动尝试重连,最多重试3次
- 每次重连间隔2秒,避免频繁请求
- 如果3次都失败,提示用户手动连接
// 伪代码:重连逻辑
int retryCount = 0;
while (retryCount < 3 && !isConnected) {
connectToDevice(device);
Thread.sleep(2000);
retryCount++;
}
if (!isConnected) {
showToast("连接失败,请手动重试");
}
避坑指南:
我曾经在重连时忘记释放旧的GATT实例,导致内存泄漏。Android上每个 connectGatt 调用都会创建一个新实例,记得在重连前调用 gatt.close()。
1.4 常见连接失败问题汇总
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| iOS扫描不到设备 | 未添加蓝牙权限、设备未广播 | 检查Info.plist中的 NSBluetoothAlwaysUsageDescription |
| Android扫描不到设备 | 未获取位置权限、蓝牙未开启 | 动态申请 ACCESS_FINE_LOCATION 权限 |
| 连接后立即断开 | 设备端连接超时、服务发现失败 | 检查设备端广播间隔和连接参数 |
| 重连失败 | 设备已离开、GATT实例未释放 | 重连前调用 close(),增加重试间隔 |
好了,这一章的内容就到这里。说白了,蓝牙扫描连接就是“找设备、连设备、保连接”这三步。iOS和Android各有各的脾气,但核心逻辑是一样的。下一章咱们聊聊数据收发和指令对接,那才是真正让美容仪动起来的关键。
记住:调试蓝牙时,多看看日志,多试试不同手机。我当年就是靠着一台iPhone 6和一台小米6,踩遍了所有坑。希望你能少走弯路。