「已注销」 2025-12-03 11:45 采纳率: 25%
浏览 3

闹钟应用退出提示问题

#设置闹钟后,只有应用在运行的时候才会提醒。退出应用后不在提示(使用了两种方案都不行)
注册:


        <!-- 服务声明 -->
        <service
            android:name=".service.AlarmJobService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="true" />

        <!-- 广播接收器 -->
        <receiver android:name=".work.AlarmReceiver"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"  />
            </intent-filter>
        </receiver>

调用代码:


class AlarmClockActivity : BaseActivity() {

    override fun getLayoutResId(): Int {
        return R.layout.act_alarm_clock
    }

    override fun initActivity() {
        checkExactAlarmPermission()
        scheduleAlarm()
    }

    // TODO: 2025/12/2 16:03 -(Administrator)-{🎉  内部调用}

    private fun checkExactAlarmPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
            if (!alarmManager.canScheduleExactAlarms())  {
                // 请求SCHEDULE_EXACT_ALARM权限
                startActivity(Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
            }
        }
    }

    private fun scheduleAlarm() {
        val calendar: Calendar = Calendar.getInstance().apply  {
            timeInMillis = System.currentTimeMillis()
            set(Calendar.HOUR_OF_DAY, 11)
            set(Calendar.MINUTE, 33)
        }

        // 确保时间在未来
        var triggerTime = calendar.timeInMillis
        if (triggerTime <= System.currentTimeMillis())  {
            triggerTime += 24 * 60 * 60 * 1000 // 推迟到第二天
        }

        // 方案1: 使用JobScheduler
        scheduleWithJobScheduler(triggerTime)

        // 方案2: 使用AlarmManager作为备用
        scheduleWithAlarmManager(triggerTime)
    }

    private fun scheduleWithJobScheduler(triggerTime: Long) {
        try {
            val jobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
            val componentName = ComponentName(this, AlarmJobService::class.java)

            val delay = triggerTime - System.currentTimeMillis()
            val jobInfo = JobInfo.Builder(0, componentName)
                .setMinimumLatency(delay)
                .setOverrideDeadline(delay + 1000)
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
                .setPersisted(true)
                .build()

            val result = jobScheduler.schedule(jobInfo)
            if (result == JobScheduler.RESULT_SUCCESS) {
                Log.d("AlarmClockActivity", "Job scheduled successfully")
            } else {
                Log.e("AlarmClockActivity", "Job scheduling failed")
                // 失败时回退到AlarmManager
                scheduleWithAlarmManager(triggerTime)
            }
        } catch (e: Exception) {
            Log.e("AlarmClockActivity", "JobScheduler error", e)
            scheduleWithAlarmManager(triggerTime)
        }
    }

    private fun scheduleWithAlarmManager(triggerTime: Long) {
        val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
        val intent = Intent(this, AlarmReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            this,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            alarmManager.setExactAndAllowWhileIdle(
                AlarmManager.RTC_WAKEUP,
                triggerTime,
                pendingIntent
            )
        } else {
            alarmManager.setExact(
                AlarmManager.RTC_WAKEUP,
                triggerTime,
                pendingIntent
            )
        }
        Log.d("AlarmClockActivity", "Alarm scheduled with AlarmManager")
    }

    // TODO: 2025/12/2 16:13 -(Administrator)-{🎉  全局调用}

    companion object {
        fun startActivity(context: Context) {
            val intent = Intent(context, AlarmClockActivity::class.java)
            context.startActivity(intent)
        }
    }
}

服务代码:


class AlarmJobService : JobService(){

    override fun onStartJob(params: JobParameters?): Boolean {
        showNotification()
        return false
    }

    override fun onStopJob(params: JobParameters?): Boolean {
        return false
    }

    private fun showNotification() {
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                "alarm_channel",
                "Alarm Notifications",
                NotificationManager.IMPORTANCE_HIGH
            )
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(this, "alarm_channel")
            .setContentTitle("闹钟")
            .setContentText("时间到了!")
            .setSmallIcon(R.mipmap.app_icon_naozhou)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setAutoCancel(true)
            .build()

        notificationManager.notify(1,  notification)
    }
}

接收器代码:


class AlarmReceiver : BroadcastReceiver(){
    @SuppressLint("ScheduleExactAlarm")
    override fun onReceive(context: Context, intent: Intent?) {
        when (intent?.action) {
            Intent.ACTION_BOOT_COMPLETED -> {
                // 设备重启后重新设置闹钟
                val prefs = context.getSharedPreferences("alarm_prefs",  Context.MODE_PRIVATE)
                val triggerTime = prefs.getLong("next_alarm_time",  0)
                if (triggerTime > 0) {
                    val alarmManager = context.getSystemService(ALARM_SERVICE)  as AlarmManager
                    val alarmIntent = Intent(context, AlarmReceiver::class.java)
                    val pendingIntent = PendingIntent.getBroadcast(
                        context,
                        0,
                        alarmIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
                    )

                    try {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            alarmManager.setExactAndAllowWhileIdle(
                                AlarmManager.RTC_WAKEUP,
                                triggerTime,
                                pendingIntent
                            )
                        } else {
                            alarmManager.setExact(
                                AlarmManager.RTC_WAKEUP,
                                triggerTime,
                                pendingIntent
                            )
                        }
                    }catch (e : Exception){
                        e.printStackTrace()
                    }
                }
            }
            else -> {
                // 处理闹钟触发
                val serviceIntent = Intent(context, AlarmJobService::class.java)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    context.startForegroundService(serviceIntent)
                } else {
                    context.startService(serviceIntent)
                }
            }
        }
    }
}
  • 写回答

2条回答 默认 最新

  • 檀越@新空间 2025-12-03 12:01
    关注

    中午好🌞🌞🌞
    本答案参考通义千问

    你提到的问题是:设置闹钟后,只有应用在运行的时候才会提醒。退出应用后不再提示,并且你已经尝试了两种方案(JobScheduler 和 AlarmManager),但都没有成功。


    问题分析

    根据你的代码和配置,以下是可能的原因:

    1. AlarmReceiver 没有正确注册或未被系统调用

      • AlarmReceiverandroid:exported="false" 表示它不能被其他应用调用,但可以通过 PendingIntent 调用。
      • 如果 AlarmReceiver 没有正确实现,或者没有处理广播,就不会触发闹钟。
    2. AlarmManager 无法在后台工作

      • Android 系统对后台服务进行了限制,尤其是从 Android 8.0 开始。
      • 即使使用 setExactAndAllowWhileIdle(),也可能因为系统优化而失效。
    3. JobScheduler 无法在后台执行任务

      • JobScheduler 是用于调度后台任务的,但它依赖于系统资源,且在某些设备上可能不保证及时执行。
    4. 权限问题

      • 你检查了 SCHEDULE_EXACT_ALARM 权限,但如果没有正确请求或用户拒绝,可能会导致功能失效。

    解决方案(重点部分加粗)

    解决方案一:确保 AlarmReceiver 正确实现

    class AlarmReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent?) {
            // 在这里启动一个 Service 或直接弹出通知
            val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            val channelId = "alarm_channel"
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val channel = NotificationChannel(
                    channelId,
                    "Alarm Channel",
                    NotificationManager.IMPORTANCE_HIGH
                )
                notificationManager.createNotificationChannel(channel)
            }
    
            val builder = NotificationCompat.Builder(context, channelId)
                .setSmallIcon(R.drawable.ic_alarm)
                .setContentTitle("闹钟提醒")
                .setContentText("您的闹钟已响起!")
                .setPriority(NotificationCompat.PRIORITY_HIGH)
    
            notificationManager.notify(1, builder.build())
        }
    }
    

    重点: 一定要在 onReceive 中执行实际操作(如弹出通知或启动 Service)。


    解决方案二:使用 Foreground Service 保持后台运行

    class AlarmService : Service() {
        override fun onBind(intent: Intent?): IBinder? {
            return null
        }
    
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            // 执行闹钟逻辑,例如播放声音、弹出通知等
            showNotification()
            return START_STICKY
        }
    
        private fun showNotification() {
            val channelId = "alarm_channel"
            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val channel = NotificationChannel(
                    channelId,
                    "Alarm Channel",
                    NotificationManager.IMPORTANCE_HIGH
                )
                notificationManager.createNotificationChannel(channel)
            }
    
            val builder = NotificationCompat.Builder(this, channelId)
                .setSmallIcon(R.drawable.ic_alarm)
                .setContentTitle("闹钟提醒")
                .setContentText("您的闹钟已响起!")
                .setPriority(NotificationCompat.PRIORITY_HIGH)
    
            startForeground(1, builder.build())
        }
    }
    

    重点: 使用 startForeground() 启动前台服务,避免被系统杀死。


    解决方案三:修改 AlarmManager 调用方式(推荐)

    private fun scheduleWithAlarmManager(triggerTime: Long) {
        val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
        val intent = Intent(this, AlarmReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            this,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            alarmManager.setExactAndAllowWhileIdle(
                AlarmManager.RTC_WAKEUP,
                triggerTime,
                pendingIntent
            )
        } else {
            alarmManager.setExact(
                AlarmManager.RTC_WAKEUP,
                triggerTime,
                pendingIntent
            )
        }
        Log.d("AlarmClockActivity", "Alarm scheduled with AlarmManager")
    }
    

    重点: 使用 setExactAndAllowWhileIdle() 而不是 setExact(),以提高闹钟在后台的可靠性。


    解决方案四:确保 AlarmReceiver 注册正确

    AndroidManifest.xml 中确保如下内容:

    <receiver android:name=".work.AlarmReceiver"
        android:enabled="true"
        android:exported="false">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    

    重点: android:exported="false" 是正确的,因为它是通过 PendingIntent 调用的,而不是外部应用。


    解决方案五:请求并验证 SCHEDULE_EXACT_ALARM 权限

    private fun checkExactAlarmPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
            if (!alarmManager.canScheduleExactAlarms()) {
                // 请求 SCHEDULE_EXACT_ALARM 权限
                val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
                startActivity(intent)
            }
        }
    }
    

    重点: 用户必须手动授予该权限,否则 setExactAndAllowWhileIdle() 不会生效。


    ✅ 总结(有序列表)

    1. 确保 AlarmReceiver 正确实现,并在 onReceive 中执行实际操作(如弹出通知)。
    2. 使用 startForeground() 启动前台服务,防止被系统杀死。
    3. 修改 AlarmManager 的调用方式,使用 setExactAndAllowWhileIdle() 提高可靠性。
    4. 确认 AlarmReceiverAndroidManifest.xml 中正确注册android:exported="false" 是正确的。
    5. 请求并验证 SCHEDULE_EXACT_ALARM 权限,确保用户已授予该权限。

    如果你仍然遇到问题,请提供以下信息:

    • 完整的 AlarmReceiver 实现
    • AndroidManifest.xml 文件内容
    • 日志输出(Logcat)中是否有错误信息

    我可以进一步帮助你排查问题。

    评论

报告相同问题?

问题事件

  • 创建了问题 12月3日