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

Android 10及以上应用关闭时通知不显示,低版本正常问题排查

Android 10+ 下 AlarmManager 通知不触发问题排查

我尝试用 AlarmManager 在指定日期展示通知,但在 Android 10 及以上版本遇到问题:仅当应用运行时通知才会显示,应用关闭后通知无法弹出。请帮忙排查以下代码中的潜在问题:


广播接收器代码

class MyNotificationReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        val taskId = intent?.getIntExtra("taskId", -1)

        if (taskId != null && taskId != -1) {
            val serviceIntent = Intent(context, MyForegroundService::class.java).apply {
                putExtra("taskId", taskId)
            }
            ContextCompat.startForegroundService(context!!, serviceIntent)
        }
    }
}

清单文件配置

<activity
    android:name=".MyNotificationActivity"
    android:showWhenLocked="true"
    android:turnScreenOn="true"
    android:exported="false" />

<service
    android:name=".utils.MyForegroundService"
    android:enabled="true"
    android:exported="false"
    android:foregroundServiceType="location"
    >
</service>

前台服务代码

class MyForegroundService : Service() {

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val taskId = intent?.getIntExtra("taskId", -1)

        if (taskId != null && taskId != -1) {
            // Load the task from the database using a coroutine
            val myTaskDao = AppDatabase.getDatabase(this).myTaskDao()
            GlobalScope.launch {
                val myTask = myTaskDao.getMyTaskById(taskId)

                // Create the notification channel
                createNotificationChannel()

                // Create the notification intent
                val notificationIntent =
                    Intent(this@MyForegroundService, MyNotificationActivity::class.java).apply {
                        putExtra("taskId", taskId)
                    }
                val pendingIntent = PendingIntent.getActivity(
                    this@MyForegroundService,
                    taskId,
                    notificationIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT
                )

                val sharedPreferences = getSharedPreferences("currency_prefs", Context.MODE_PRIVATE)
                val selectedCurrency = sharedPreferences.getString("selected_currency", "USD")
                val currencySymbols = mapOf("USD" to "$", "EUR" to "€", "GBP" to "£", "JPY" to "¥")
                // Get the symbol for the selected currency
                val selectedCurrencySymbol = currencySymbols[selectedCurrency] ?: "$"

                // Create the notification
                val notification = NotificationCompat.Builder(this@MyForegroundService, CHANNEL_ID)
                    .setContentTitle("Payment for ${myTask[0].description}")
                    .setContentText("Total amount ${selectedCurrencySymbol + myTask[0].price} is due.")
                    .setSmallIcon(myTask[0].image)
                    .setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setContentIntent(pendingIntent) // Set the pending intent
                    .setAutoCancel(false) // Make the notification dismiss when clicked
                    .build()

                // Start the service in the foreground
                startForeground(NOTIFICATION_ID, notification)
            }
        }

        // Return START_STICKY to ensure the service is restarted if it gets killed by the system
        return START_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channelName = "My Channel"
            val channelDescription = "Notification Channel"

            val importance = NotificationManager.IMPORTANCE_HIGH

            val channel = NotificationChannel(CHANNEL_ID, channelName, importance).apply {
                description = channelDescription
            }

            val notificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }


    override fun onTaskRemoved(rootIntent: Intent?) {
        val restartServiceIntent = Intent(applicationContext, this.javaClass)
        restartServiceIntent.setPackage(packageName)
        val restartServicePendingIntent = PendingIntent.getService(
            applicationContext, 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT
        )
        val alarmService: AlarmManager =
            applicationContext.getSystemService(ALARM_SERVICE) as AlarmManager
        alarmService.set(
            AlarmManager.ELAPSED_REALTIME,
            SystemClock.elapsedRealtime() + 1000,
            restartServicePendingIntent
        )
        super.onTaskRemoved(rootIntent)
    }

    private val CHANNEL_ID = "my_channel"
    private val NOTIFICATION_ID = 1
}

Activity 中的代码

val alarmManager =
    requireActivity().getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(requireActivity(), MyNotificationReceiver::class.java)
intent.putExtra("taskId", myTaskId.toInt())

val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    PendingIntent.getBroadcast(
        requireContext(),
        0,
        intent,
        PendingIntent.FLAG_MUTABLE // add the FLAG_MUTABLE flag here
    )
} else {
    PendingIntent.getBroadcast(requireContext(), 0, intent, 0)
}
// Set the alarm to start at the specified time
alarmManager.set(AlarmManager.RTC_WAKEUP, customTime, pendingIntent)

核心问题排查及修复方案

1. AlarmManager API 适配问题

Android 12+ 对后台应用的普通闹钟触发有严格限制,AlarmManager.set() 无法在应用关闭时触发。需根据系统版本选择适配API:

  • Android 12+:使用 AlarmManager.setExactAndAllowWhileIdle() 或 AlarmManager.setAndAllowWhileIdle(),这两个方法允许设备空闲、应用后台时触发闹钟。
  • 低版本:可继续使用 set(),但建议统一用 setExactAndAllowWhileIdle() 保证全版本兼容性。

2. PendingIntent 复用冲突

创建广播 PendingIntent 时,你传入的 requestCode 是固定值0,多个任务的闹钟会互相覆盖。需换成唯一值,比如用 taskId:

val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    PendingIntent.getBroadcast(
        requireContext(),
        taskId, // 用taskId作为唯一标识
        intent,
        PendingIntent.FLAG_MUTABLE
    )
} else {
    PendingIntent.getBroadcast(requireContext(), taskId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}

3. 前台服务类型错误

你的服务声明了android:foregroundServiceType="location",但该服务仅用于展示通知,和位置功能无关。Android 12+ 要求前台服务必须声明正确类型,否则会被系统限制,应修改为:

<service
    android:name=".utils.MyForegroundService"
    android:enabled="true"
    android:exported="false"
    android:foregroundServiceType="shortService"
    >
</service>

Android 13+ 可直接使用notification类型:

android:foregroundServiceType="notification"

4. 前台服务启动超时

Android 8.0+ 要求调用startForegroundService()后,必须在5秒内调用startForeground()。你当前在异步协程中执行数据库查询,极易触发超时,导致服务被系统杀死。

修复方案:先展示临时前台通知,完成数据加载后再更新通知内容:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    val taskId = intent?.getIntExtra("taskId", -1)
    if (taskId != null && taskId != -1) {
        // 先创建临时前台通知,避免超时
        createNotificationChannel()
        val tempNotification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("加载中...")
            .setSmallIcon(R.drawable.ic_placeholder) // 替换为你的占位图标
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .build()
        startForeground(NOTIFICATION_ID, tempNotification)

        // 异步加载数据并更新通知
        val myTaskDao = AppDatabase.getDatabase(this).myTaskDao()
        GlobalScope.launch(Dispatchers.IO) {
            val myTask = myTaskDao.getMyTaskById(taskId)
            // 构建最终通知逻辑...
            val finalNotification = NotificationCompat.Builder(this@MyForegroundService, CHANNEL_ID)
                // 填充最终通知内容
                .build()
            // 更新前台通知
            (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
                .notify(NOTIFICATION_ID, finalNotification)
        }
    }
    return START_NOT_STICKY // 任务完成后无需重启服务
}

5. 广播接收器未注册

你的代码未展示广播接收器的清单注册,Android 10+ 显式广播接收器必须在清单中声明:

<receiver
    android:name=".MyNotificationReceiver"
    android:exported="false" />

6. 后台启动规范问题

Android 10+ 对后台服务启动限制严格,若应用被完全杀死,广播接收器触发后启动服务可能被拦截。建议用 WorkManager 替代前台服务,更符合后台规范:

// 广播接收器中直接调度WorkManager
class MyNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        val taskId = intent?.getIntExtra("taskId", -1)
        if (taskId != null && taskId != -1) {
            val workRequest = OneTimeWorkRequestBuilder<NotificationWorker>()
                .setInputData(Data.Builder().putInt("taskId", taskId).build())
                .build()
            WorkManager.getInstance(context!!).enqueue(workRequest)
        }
    }
}

// 实现Worker处理通知逻辑
class NotificationWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        val taskId = inputData.getInt("taskId", -1)
        // 这里执行原前台服务中的通知构建逻辑
        return Result.success()
    }
}

内容的提问来源于stack exchange,提问作者kbshah Asif

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 13:14:57