Swift 3:设备锁屏时如何让本地通知持续响铃
刚好做过类似的需求!要实现锁屏下本地通知持续响铃直到用户点击,核心是把「通知触发」和「持续音频播放」绑定,还要处理好后台权限和用户交互。下面分iOS和Android两个平台给你具体的实现思路和代码:
iOS 实现方案
iOS默认的本地通知铃声是单次短音,要实现像闹钟一样的持续响铃,得结合后台音频播放和通知代理来处理:
步骤1:配置权限与资源
- 把循环格式的音频文件(比如
.caf或.mp3)加入项目,建议用可以无缝循环的音频。 - 在
Info.plist中添加UIBackgroundModes键,包含audio值,开启后台音频权限。
步骤2:调度本地通知
用UNCalendarNotificationTrigger结合日期选择器的时间来创建通知,同时配置自定义铃声:
import AVFoundation import UserNotifications // 提前配置音频会话,确保后台能播放 func setupAudioSession() { let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playback, mode: .default, options: .mixWithOthers) try session.setActive(true) } catch { print("音频会话配置失败: \(error)") } } // 调度闹钟通知 func scheduleAlarm(at triggerDate: Date) { setupAudioSession() let content = UNMutableNotificationContent() content.title = "闹钟提醒" content.body = "该起床啦!" // 自定义循环铃声 content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "looping_alarm.caf")) content.categoryIdentifier = "ALARM_CATEGORY" // 从日期选择器的时间生成触发条件 let dateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: triggerDate) let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false) let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) }
步骤3:处理通知触发与交互
通过UNUserNotificationCenterDelegate监听通知状态,触发时启动循环音频,用户点击时停止播放:
var audioPlayer: AVAudioPlayer? extension AppDelegate: UNUserNotificationCenterDelegate { // 用户点击通知时停止音频 func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { stopAlarmAudio() completionHandler() } // 前台收到通知时启动音频 func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { startAlarmAudio() completionHandler([.sound, .alert]) } // 后台收到通知时启动音频 func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { startAlarmAudio() completionHandler(.newData) } } // 启动循环音频 func startAlarmAudio() { guard let audioURL = Bundle.main.url(forResource: "looping_alarm", withExtension: "caf") else { return } do { audioPlayer = try AVAudioPlayer(contentsOf: audioURL) audioPlayer?.numberOfLoops = -1 // 无限循环 audioPlayer?.play() } catch { print("音频播放失败: \(error)") } } // 停止音频 func stopAlarmAudio() { audioPlayer?.stop() audioPlayer = nil }
Android 实现方案
Android的后台限制更严格,要实现持续响铃需要用前台服务来播放音频,避免被系统杀死:
步骤1:配置权限与通知渠道
- 在
AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- 创建高优先级的通知渠道,确保锁屏时能显示:
private fun createAlarmChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( "ALARM_CHANNEL", "闹钟通知", NotificationManager.IMPORTANCE_HIGH ).apply { description = "持续响铃的闹钟通知渠道" setSound( RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM), AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ALARM) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build() ) lockscreenVisibility = Notification.VISIBILITY_PUBLIC } val notificationManager = getSystemService(NotificationManager::class.java) notificationManager.createNotificationChannel(channel) } }
步骤2:创建前台服务播放音频
服务中启动循环音频,同时显示前台通知:
class AlarmService : Service() { private var mediaPlayer: MediaPlayer? = null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // 启动循环铃声 mediaPlayer = MediaPlayer.create(this, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)) mediaPlayer?.isLooping = true mediaPlayer?.start() // 创建前台通知,点击可停止闹钟 val stopIntent = Intent(this, MainActivity::class.java).apply { action = "STOP_ALARM" } val pendingIntent = PendingIntent.getActivity( this, 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val notification = NotificationCompat.Builder(this, "ALARM_CHANNEL") .setContentTitle("闹钟提醒") .setContentText("该起床啦!") .setSmallIcon(R.drawable.ic_alarm) .setContentIntent(pendingIntent) .setPriority(NotificationCompat.PRIORITY_HIGH) .setFullScreenIntent(pendingIntent, true) // 锁屏时直接显示交互界面 .build() startForeground(1, notification) return START_STICKY } override fun onDestroy() { super.onDestroy() mediaPlayer?.stop() mediaPlayer?.release() } override fun onBind(intent: Intent): IBinder? = null }
步骤3:调度闹钟触发服务
用AlarmManager结合日期选择器的时间来触发前台服务:
fun scheduleAlarm(triggerDate: Date) { createAlarmChannel() val alarmIntent = Intent(this, AlarmService::class.java) val pendingIntent = PendingIntent.getService( this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val alarmManager = getSystemService(AlarmManager::class.java) alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerDate.time, pendingIntent ) }
步骤4:处理通知点击停止闹钟
在MainActivity中监听停止闹钟的意图:
override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) if ("STOP_ALARM" == intent?.action) { stopService(Intent(this, AlarmService::class.java)) val notificationManager = getSystemService(NotificationManager::class.java) notificationManager.cancel(1) } }
关键注意事项
- iOS:确保在
AppDelegate中设置UNUserNotificationCenter.current().delegate = self,并申请通知权限。 - Android:Android 13+需要动态申请
POST_NOTIFICATIONS权限,测试时要允许应用后台活动,避免被系统杀死。
内容的提问来源于stack exchange,提问作者Ankur sharma




