You need to enable JavaScript to run this app. 原生Swift iOS应用如何实现推送到达时同RN一致的后台持续振动效果
优惠活动
大模型
产品
解决方案
定价
更多

原生Swift iOS应用如何实现推送到达时同RN一致的后台持续振动效果

iOS原生Swift实现全场景后台/杀进程振动响铃方案

核心原理说明

你观察到的RN实现效果,根本不是依赖普通的react-native-vibrate模块的前台接口,而是底层用了VoIP推送 + PushKit + CallKit的系统级通话方案,普通APNs推送和前台振动接口本身就不支持后台/杀进程场景生效,这是你之前尝试失败的根本原因。

具体实现步骤

1. 配置项目基础权限

  • 打开项目Signing & Capabilities标签页,添加2个能力:
    • Push Notifications:开启推送权限
    • Background Modes:勾选Voice over IP、Remote notifications、Audio, AirPlay, and Picture in Picture三个选项
  • 申请苹果开发者后台的VoIP推送证书,替换原有普通APNs证书

2. 集成PushKit接收VoIP推送

VoIP推送可以唤醒哪怕被杀死的APP,获得最多几秒的后台代码执行权限,实现回调:

import PushKit

class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let pushRegistry = PKPushRegistry(queue: DispatchQueue.main)
        pushRegistry.delegate = self
        pushRegistry.desiredPushTypes = [.voIP]
        return true
    }
    
    func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        // 把VoIP的deviceToken传给后端,用于发推送
        let token = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()
        print("VoIP token: \(token)")
    }
    
    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        // 收到视频通话推送,在这里处理振铃和振动
        handleIncomingCall()
        completion()
    }
}

3. 实现持续振动+响铃

拿到后台执行权限后,先配置音频会话保证后台播放不被挂起,再用GCD定时器触发重复振动,不要用普通Timer,后台会被暂停:

import AVFoundation
import AudioToolbox

var vibrateTimer: DispatchSourceTimer?
var audioPlayer: AVAudioPlayer?

func handleIncomingCall() {
    // 配置音频会话,支持后台播放
    do {
        let session = AVAudioSession.sharedInstance()
        try session.setCategory(.playback, mode: .default, options: [.mixWithOthers])
        try session.setActive(true)
    } catch {
        print("音频会话配置失败: \(error)")
    }
    
    // 循环播放铃声
    if let ringtonePath = Bundle.main.path(forResource: "ringtone", ofType: "wav") {
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: ringtonePath))
            audioPlayer?.numberOfLoops = -1 // 无限循环
            audioPlayer?.play()
        } catch {
            print("铃声播放失败: \(error)")
        }
    }
    
    // 每秒触发一次振动,持续30秒
    vibrateTimer = DispatchSource.makeTimerSource(queue: .global())
    vibrateTimer?.schedule(deadline: .now(), repeating: 1.0, leeway: .milliseconds(100))
    vibrateTimer?.setEventHandler {
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
    }
    vibrateTimer?.resume()
    
    // 30秒后自动停止
    DispatchQueue.global().asyncAfter(deadline: .now() + 30) {
        vibrateTimer?.cancel()
        vibrateTimer = nil
        audioPlayer?.stop()
    }
}

4. 杀进程场景兼容(必选)

iOS 13之后要求收到VoIP推送必须调用CallKit上报系统来电,否则会被系统限制推送权限。集成CallKit之后,系统会自动处理来电的响铃、振动、锁屏弹窗,完全覆盖所有场景,和系统电话体验一致,也是RN项目实际依赖的底层能力。

如果不需要兼容iOS 12及以下版本,直接用CallKit的系统振铃能力即可,不需要自己实现振动和响铃逻辑,系统会自动处理,稳定性更高。

常见问题说明

  • 之前用AudioServicesPlayAlertSound后台无效的原因:普通APP进入后台10秒左右就会被系统挂起,所有代码停止执行,只有特殊后台模式(如音频播放、VoIP)能获得持续执行权限
  • 不要尝试用普通后台刷新、静默推送实现该需求,触发频率和执行时间都被系统严格限制,无法稳定实现即时振铃效果

内容的提问来源于stack exchange,提问作者Vishal Gabani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.11 22:45:00