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
相关产品推荐
相关产品推荐

