You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动