You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter iOS应用集成Polar BLE SDK 5.11.0+调用startAutoConnectToDevice时发生死锁

Flutter iOS应用集成Polar BLE SDK 5.11.0+调用startAutoConnectToDevice时发生死锁

我最近在开发一款基于Flutter的iOS心率监测应用,集成Polar BLE SDK时遇到了一个棘手的死锁问题:SDK从5.10.0升级到5.11.0及以上版本(包括最新的6.5.0)后,调用startAutoConnectToDevice方法会导致应用完全冻结,而5.10.0版本则一切正常。下面是完整的问题分析和解决方案:


环境信息

  • Flutter版本:3.35.2+
  • iOS版本:17.x
  • Polar BLE SDK版本:5.11.0+(5.10.0可正常运行)
  • 测试设备:iPhone实体机
  • 心率监测硬件:Polar/Wahoo TICKR

最小复现代码

Flutter端代码(main.dart)

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  static const platform = MethodChannel('com.example.app/polar');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: TextButton(
          onPressed: () {
            platform.invokeMethod('autoConnectToDevice');
          },
          child: const Text('Connect'),
        ),
      ),
    );
  }
}

iOS端代码

HrService.swift

import PolarBleSdk
import RxSwift
import CoreBluetooth

class HrService: PolarBleApiObserver {
    var api: PolarBleApi? = nil
    private var autoConnectDisposable: Disposable?

    func initializeApi() {
        api = PolarBleApiDefaultImpl.polarImplementation(DispatchQueue.main, features: [])
        api!.observer = self
    }

    func deviceConnecting(_ identifier: PolarDeviceInfo) {
        NSLog("deviceConnecting")
    }

    func deviceConnected(_ identifier: PolarDeviceInfo) {
        NSLog("deviceConnected")
    }

    func deviceDisconnected(_ identifier: PolarDeviceInfo, pairingError: Bool) {
        NSLog("deviceDisconnected")
    }

    func autoConnectToDevice(result: @escaping FlutterResult) {
        guard let api = api else {
            return result(FlutterError(code: "polarApiNil", message: nil, details: nil))
        }
        autoConnectDisposable?.dispose()
        autoConnectDisposable = api.startAutoConnectToDevice(-80, service: CBUUID(string: "180D"), polarDeviceType: nil)
            .subscribe { e in
                switch e {
                case .completed:
                    result(nil)
                case .error(_):
                    result(FlutterError())
                @unknown default:
                    break
                }
            }
    }
}

AppDelegate.swift

@main
@objc class AppDelegate: FlutterAppDelegate {
    let POLAR_METHOD_CHANNEL = "com.example.app/polar"
    let hrService = HrService()

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller = window?.rootViewController as! FlutterViewController
        let polarMethodChannel = FlutterMethodChannel(name: POLAR_METHOD_CHANNEL, binaryMessenger: controller.binaryMessenger)
        
        hrService.initializeApi()
        
        polarMethodChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) in
            if call.method == "autoConnectToDevice" {
                self.hrService.autoConnectToDevice(result: result)
            }
        })
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

死锁现象与栈追踪

点击"Connect"按钮后应用完全无响应,主线程栈追踪显示死锁发生在AtomicType的锁操作上:

Thread 1 Queue : com.apple.main-thread (serial)
#0 0x00000001e2126628 in __psynch_mutexwait ()
#1 0x00000001fcc6ac94 in _pthread_mutex_firstfit_lock_wait ()
#2 0x00000001fcc69cac in _pthread_mutex_firstfit_lock_slow ()
#3 0x0000000102ba25b0 in AtomicType.accessItem(_:) at .../AtomicType.swift:26
#4 0x000000010669fcbc in CBScanner.scanningNeeded() at .../CBScanner.swift:133
#5 0x0000000106686170 in CBDeviceListenerImpl.updateSessionState(_:state:)
#6 0x000000010668cd80 in CBDeviceListenerImpl.openSessionDirect(_:)
#7 0x000000010681f248 in closure #2 in PolarBleApiImpl.startAutoConnectToDevice(_:service:polarDeviceType:)
...
#29 0x0000000102ba25f4 in AtomicType.accessItem(_:) at .../AtomicType.swift:27
#30 0x0000000102c10e08 in closure #3 in CBDeviceListenerImpl.handleDeviceDiscovered(_:didDiscover:advertisementData:rssi:)

根因分析

对比Polar BLE SDK 5.10.0和5.11.0的代码,发现CBScanner.swift中的scanObservers变量实现发生了变更,目的是提升线程安全性,但引入了死锁风险:

// 5.10.0版本实现
var scanObservers = Set<RxObserver<BleDeviceSession>>()
func addClient(_ scanner: RxObserver<BleDeviceSession>){
    scanObservers.insert(scanner)
    self.commandState(ScanAction.clientStartScan)
}

// 5.11.0+版本实现
var scanObservers = AtomicType(initialValue: Set<RxObserver<BleDeviceSession>>())
func addClient(_ scanner: RxObserver<BleDeviceSession>){
    scanObservers.accessItem { $0.insert(scanner) }
    self.commandState(ScanAction.clientStartScan)
}

死锁的核心原因是:主线程在调用startAutoConnectToDevice时已经持有了AtomicType的锁,后续的SDK内部回调又在同一个主线程中再次尝试获取该锁,导致线程自身死锁。


已尝试的无效方案

  • 更换Polar API初始化队列:试过主线程、后台队列、自定义串行队列
  • 异步包装调用:将startAutoConnectToDevice包裹在各种DispatchQueue.async中执行
  • 延迟执行:使用DispatchQueue.main.asyncAfter延迟调用接口

有效解决方案

问题的关键是避免在同一个线程中嵌套获取锁,我们可以通过将Polar SDK的核心操作切换到独立的串行队列来打破死锁链:

修改iOS端HrService.swift代码

  1. 创建独立的串行队列,避免与主线程共享:
private let polarQueue = DispatchQueue(label: "com.example.app.polar.queue", qos: .utility)
  1. 初始化Polar API时使用该独立队列:
func initializeApi() {
    api = PolarBleApiDefaultImpl.polarImplementation(polarQueue, features: [])
    api!.observer = self
}
  1. 在独立队列中执行startAutoConnectToDevice,并将结果回调切回主线程(符合Flutter Method Channel要求):
func autoConnectToDevice(result: @escaping FlutterResult) {
    guard let api = api else {
        return result(FlutterError(code: "polarApiNil", message: "Polar API未初始化", details: nil))
    }
    
    polarQueue.async { [weak self] in
        self?.autoConnectDisposable?.dispose()
        self?.autoConnectDisposable = api.startAutoConnectToDevice(-80, service: CBUUID(string: "180D"), polarDeviceType: nil)
            .subscribe(on: MainScheduler.instance) // 回调切回主线程
            .subscribe { e in
                switch e {
                case .completed:
                    result(nil)
                case .error(let err):
                    result(FlutterError(code: "connect_failed", message: err.localizedDescription, details: nil))
                @unknown default:
                    break
                }
            }
    }
}
  1. 确保PolarBleApiObserver的回调在主线程执行(如果需要更新UI或通知Flutter):
func deviceConnected(_ identifier: PolarDeviceInfo) {
    DispatchQueue.main.async {
        NSLog("deviceConnected: \(identifier.deviceId)")
        // 若需通知Flutter,在此处调用Method Channel
    }
}

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 03:08:49