You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 16:20:41