使用Alarm Manager设置通知后应用崩溃问题求助
问题分析与解决方案
核心问题点
- 异步选择器的时间获取错误:调用
datespicker.show()和timerpicker.show()后立刻读取日期时间,此时用户尚未完成选择,拿到的是初始默认值,导致Alarm时间设置错误,可能触发立刻执行甚至重复触发,引发崩溃。 - PendingIntent Flag缺失:Android 12及以上(targetSdkVersion ≥31)要求创建PendingIntent必须指定
FLAG_IMMUTABLE或FLAG_MUTABLE,否则会抛出异常导致崩溃。 - Calendar小时制错误:使用
Calendar.HOUR(12小时制)会导致时间判断错误,应改用Calendar.HOUR_OF_DAY(24小时制)。 - BroadcastReceiver中启动Fragment错误:Intent无法直接启动Fragment,必须启动包含该Fragment的宿主Activity。
- Notification兼容性问题:直接使用
Notification.Builder仅支持Android O及以上,低版本设备会崩溃,建议用NotificationCompat.Builder做兼容。
修复后的代码
1. 修正BroadcastReceiver代码
class AlarmReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { context ?: return // 启动宿主Activity而非直接启动Fragment val launchIntent = Intent(context, YourHostActivity::class.java).apply { // 可通过Intent传参,在Activity中处理跳转指定Fragment的逻辑 putExtra("target_fragment", "add") flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP } val pendingIntentFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { PendingIntent.FLAG_IMMUTABLE } else { 0 } val pendingIntent = TaskStackBuilder.create(context) .addNextIntentWithParentStack(launchIntent) .getPendingIntent(0, pendingIntentFlag) val notificationBuilder = NotificationCompat.Builder(context, "channel1") .setContentTitle("任务提醒") .setContentText("该完成你的任务啦!") .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(pendingIntent) .setAutoCancel(true) .setPriority(NotificationCompat.PRIORITY_DEFAULT) val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager manager.notify(0, notificationBuilder.build()) } }
2. 修正界面逻辑代码
// 存储用户选择的日期时间 private var selectedYear = 0 private var selectedMonth = 0 private var selectedDay = 0 private var selectedHour = 0 private var selectedMinute = 0 ofonbutton.setOnClickListener { if (ofonbutton.text == "ON") { date.visibility = View.GONE datedays.visibility = View.GONE datehour.visibility = View.GONE ofonbutton.text = "OFF" cancelAlarm() } else { ofonbutton.text = "ON" chosetime() } } private fun chosetime() { val calendar = Calendar.getInstance() selectedYear = calendar.get(Calendar.YEAR) selectedMonth = calendar.get(Calendar.MONTH) selectedDay = calendar.get(Calendar.DAY_OF_MONTH) selectedHour = calendar.get(Calendar.HOUR_OF_DAY) selectedMinute = calendar.get(Calendar.MINUTE) // 先选日期,再选时间 val datePickerDialog = DatePickerDialog(requireActivity(), { _, year, month, dayOfMonth -> selectedYear = year selectedMonth = month selectedDay = dayOfMonth showTimePicker() }, selectedYear, selectedMonth, selectedDay) datePickerDialog.show() } private fun showTimePicker() { val timePickerDialog = TimePickerDialog(requireActivity(), { _, hourOfDay, minute -> selectedHour = hourOfDay selectedMinute = minute // 更新UI显示 requireActivity().findViewById<TextView>(R.id.date).visibility = View.VISIBLE requireActivity().findViewById<TextView>(R.id.datebldays).apply { visibility = View.VISIBLE text = "$selectedYear/${selectedMonth + 1}/$selectedDay" // 月份+1,Calendar.MONTH从0开始 } requireActivity().findViewById<TextView>(R.id.dateblhours).apply { visibility = View.VISIBLE text = "at $selectedHour:$selectedMinute" } // 选完时间后再创建Alarm createChannel() setAlarm() }, selectedHour, selectedMinute, false) timePickerDialog.show() } private fun createChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel("channel1", "任务提醒频道", NotificationManager.IMPORTANCE_DEFAULT).apply { enableVibration(true) } val manager = requireActivity().getSystemService(NotificationManager::class.java) manager.createNotificationChannel(channel) } } private fun setAlarm() { val alarmManager = requireActivity().getSystemService(ALARM_SERVICE) as AlarmManager val intent = Intent(requireActivity(), AlarmReceiver::class.java) val pendingIntentFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { PendingIntent.FLAG_IMMUTABLE } else { 0 } val pendingIntent = PendingIntent.getBroadcast(requireActivity(), 0, intent, pendingIntentFlag) val calendar = Calendar.getInstance().apply { set(Calendar.YEAR, selectedYear) set(Calendar.MONTH, selectedMonth) set(Calendar.DAY_OF_MONTH, selectedDay) set(Calendar.HOUR_OF_DAY, selectedHour) set(Calendar.MINUTE, selectedMinute) set(Calendar.SECOND, 0) // 若设置时间已过去,自动延后一天避免立刻触发 if (timeInMillis < System.currentTimeMillis()) { add(Calendar.DAY_OF_MONTH, 1) } } // 根据系统版本选择合适的Alarm设置方式 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent) } else { alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent) } // 如需重复提醒,取消上方注释(注意Android 12+重复Alarm间隔不能小于15分钟) // alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, AlarmManager.INTERVAL_DAY, pendingIntent) } private fun cancelAlarm() { val alarmManager = requireActivity().getSystemService(ALARM_SERVICE) as AlarmManager val intent = Intent(requireActivity(), AlarmReceiver::class.java) val pendingIntentFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_NO_CREATE } else { PendingIntent.FLAG_NO_CREATE } val pendingIntent = PendingIntent.getBroadcast(requireActivity(), 0, intent, pendingIntentFlag) pendingIntent?.let { alarmManager.cancel(it) it.cancel() } }
额外注意事项
- Manifest配置:在AndroidManifest.xml中注册BroadcastReceiver:
<receiver android:name=".AlarmReceiver" />
若需返回栈,给宿主Activity配置父Activity:
<activity android:name=".YourHostActivity" android:parentActivityName=".MainActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".MainActivity" /> </activity>
- Android 12+限制:重复Alarm间隔不得小于15分钟,
setInexactRepeating精度有限,如需精确重复提醒,建议每次触发后重新设置Alarm。 - 后台限制:Android 8.0及以上禁止BroadcastReceiver执行耗时操作,如需复杂逻辑,建议使用WorkManager或JobScheduler。
内容的提问来源于stack exchange,提问作者Idriss Amari
相关产品推荐
相关产品推荐

