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

Swift/Flutter 后台使用CFNotificationCenterAddObserver监听屏幕唤醒问题

问题根因

休眠超过5-7秒后无法接收通知是iOS系统的默认机制决定的,和你的代码逻辑无关:

  • 应用进入后台后,系统默认只会预留3-10秒的后台执行时间,到期后会直接将应用挂起,此时应用所有线程被冻结,不会执行任何代码,自然无法接收com.apple.springboard.hasBlankedScreen的Darwin通知。短时间休眠能收到回调,本质是应用还没到系统挂起的时间窗口。
  • 你设置的CFNotificationSuspensionBehavior.deliverImmediately仅在应用处于前台、或后台活跃未挂起状态时生效,应用挂起后该参数完全无效,系统会将期间的所有通知合并缓存,等应用回到前台后才会投递,无法做到实时触发。
  • 额外提醒:com.apple.springboard.hasBlankedScreen属于未公开的系统私有通知,直接使用存在App Store审核被拒的风险,且不同iOS大版本的通知触发逻辑可能发生无提示变更。
可行解决方案

根据你的业务场景选择对应方案,所有方案均符合苹果审核规则:

方案1:使用官方公开API实现状态监听(前台场景最优)

如果你的需求仅需要在应用处于前台、或用户使用应用功能时感知屏幕/应用状态,完全不需要监听私有Darwin通知,用系统公开API即可稳定实现,不需要任何特殊权限:

// 在AppDelegate或对应业务控制器中添加监听
override func viewDidLoad() {
    super.viewDidLoad()
    // 监听屏幕连接/断开(对应点亮、锁屏事件)
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleScreenStateChange),
        name: UIScreen.didConnectNotification,
        object: nil
    )
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleScreenStateChange),
        name: UIScreen.didDisconnectNotification,
        object: nil
    )
    // 监听应用前后台切换
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleAppStateChange),
        name: UIApplication.didBecomeActiveNotification,
        object: nil
    )
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleAppStateChange),
        name: UIApplication.willResignActiveNotification,
        object: nil
    )
}

@objc private func handleScreenStateChange() {
    // 结合数据保护状态判断是否真的锁屏
    let isLocked = !UIApplication.shared.isProtectedDataAvailable
    // 这里触发Flutter端的MethodChannel回调即可
}

@objc private func handleAppStateChange() {
    let isAppForeground = UIApplication.shared.applicationState == .active
    // 结合前后台状态和屏幕状态,就能实现你需要的判断逻辑
}

deinit {
    // 记得移除监听,避免内存泄漏
    NotificationCenter.default.removeObserver(self)
}

对应Dart端的代码不需要做逻辑调整,只需要匹配回调的方法名即可,注意在Widget的dispose生命周期里取消MethodCallHandler的绑定。

方案2:申请匹配业务的后台模式(需要后台实时监听场景)

如果你的业务确实需要应用在退到后台后仍然实时感知屏幕状态,必须申请对应场景的合法后台权限,让应用不会被系统快速挂起:

  • 打开Xcode项目的Signing & Capabilities页,添加Background Modes能力
  • 勾选和你应用核心业务完全匹配的后台选项:比如音频类应用选Audio, AirPlay, and Picture in Picture、导航类应用选Location updates、运动类应用选External accessory communication
  • 开启对应后台模式后,应用在后台会保持活跃状态,可以正常接收系统通知
    注意:绝对不要为了保活勾选和业务无关的后台模式,也不要使用静音音频循环、伪造定位等违规保活手段,这类操作是苹果审核的重点排查项,一旦发现会直接拒绝上架甚至下架已上线应用。

方案3:VoIP推送唤醒(即时通讯类场景适用)

如果你的应用属于即时通讯类产品,可以通过服务端发送VoIP推送,在屏幕状态变化时唤醒处于挂起状态的应用,应用被唤醒后会获得30秒左右的执行时间,足够获取当前屏幕状态并回调给Flutter层,该方案不需要应用长期驻留后台,但需要服务端配合实现推送逻辑。

现有代码的优化建议
  • 你当前Swift端用Unmanaged.passUnretained传递self作为观察者,不会持有对象,但需要在不需要监听时主动调用CFNotificationCenterRemoveObserver移除通知监听,否则对象释放后会触发野指针崩溃。
  • 不要每次收到通知都重新初始化FlutterMethodChannel,可以将Channel实例设为全局单例或AppDelegate的属性,避免重复创建对象带来的性能开销。

内容的提问来源于stack exchange,提问作者Colpo Matteo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 10:01:00