Android应用周期性自启动实现及FCM通知厂商干扰问题解决
1. 实现Android应用周期性自启动以接收通知
首先得明确:Android从8.0开始对后台启动管控越来越严格,国内厂商还会叠加额外限制,所以必须用官方推荐的合规方案,尽量降低被系统拦截的概率:
使用WorkManager(官方首选)
WorkManager是Jetpack组件,专门处理延迟/周期性后台任务,能自动适配不同Android版本,哪怕应用被划掉、设备重启,只要满足约束条件就会执行。
示例代码:- 定义Worker类:
class NotificationCheckWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { // 在这里执行通知检查、请求服务器拉取通知等逻辑 return Result.success() } }- 配置并启动周期性任务:
val periodicRequest = PeriodicWorkRequestBuilder<NotificationCheckWorker>(1, TimeUnit.HOURS) .setConstraints(Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) // 可选:仅在有网络时执行 .build()) .build() WorkManager.getInstance(context).enqueueUniquePeriodicWork( "NotificationCheck", ExistingPeriodicWorkPolicy.KEEP, periodicRequest )AlarmManager(适合时间精度要求高的场景)
Android 12+对精确闹钟要求SCHEDULE_EXACT_ALARM权限,普通重复闹钟会被系统延迟调度。如果用AlarmManager,需要配合BroadcastReceiver:
示例代码:- 动态注册BroadcastReceiver(Android8+静态注册大部分广播失效):
class AlarmReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { // 执行通知接收逻辑,比如启动服务处理 context?.startForegroundService(Intent(context, NotificationService::class.java)) } }- 设置重复闹钟:
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(context, AlarmReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android12+需先申请精确闹钟权限 if (alarmManager.canScheduleExactAlarms()) { alarmManager.setRepeating( AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 3600000, // 1小时重复一次 pendingIntent ) } } else { alarmManager.setRepeating( AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 3600000, pendingIntent ) }关键注意事项
- 申请必要权限:比如
RECEIVE_BOOT_COMPLETED(设备重启后重新调度任务)、SCHEDULE_EXACT_ALARM(若使用精确闹钟)。 - 引导用户添加厂商自启动白名单:国内小米、华为、OPPO等厂商会拦截未授权的后台任务,必须让用户手动在系统设置里开启应用的自启动权限,否则上述方法可能失效。
- 申请必要权限:比如
2. 解决FCM通知被厂商拦截的问题
这个问题核心是国内厂商的后台进程管控:用户划掉应用栈时,系统会强制杀死进程,导致FCM推送服务无法正常运行。结合你提到的「打开应用能获取到未展示的通知」,可以试试这些方案:
引导用户开启核心权限与白名单
这是最直接有效的方法,厂商拦截逻辑主要针对未授权应用:- 开启自启动权限:不同厂商路径不同,比如小米:设置→应用设置→应用管理→你的应用→权限管理→自启动;华为:设置→应用和服务→应用启动管理→你的应用→手动管理→允许自启动。
- 开启忽略电池优化:设置→电池→电池优化→找到你的应用→选择「不允许」。
- 开启后台弹出权限:部分厂商会拦截后台通知弹出,需开启此权限。
优化FCM消息配置
- 使用高优先级通知:发送FCM消息时,将
priority设为high(Android)或10(iOS),系统会优先处理这条通知,哪怕应用在后台。 - 结合数据消息:数据消息会触发
FirebaseMessagingService的onMessageReceived方法(应用后台时也能触发,但进程被杀死后不行),你可以在这个方法里手动创建本地通知,确保展示:
class MyFirebaseMessagingService : FirebaseMessagingService() { override fun onMessageReceived(remoteMessage: RemoteMessage) { super.onMessageReceived(remoteMessage) // 处理数据消息,构建并展示本地通知 val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notification = NotificationCompat.Builder(this, "NOTIFICATION_CHANNEL") .setContentTitle(remoteMessage.data["title"]) .setContentText(remoteMessage.data["content"]) .setSmallIcon(R.drawable.ic_notification) .build() notificationManager.notify(1, notification) } }- 使用高优先级通知:发送FCM消息时,将
启动前台服务
如果你的应用有合法前台场景(比如音乐播放、定位追踪),可以启动前台服务,系统不会轻易杀死前台进程,FCM服务就能持续运行。注意要显示合规的前台通知:class NotificationService : Service() { override fun onCreate() { super.onCreate() val notification = NotificationCompat.Builder(this, "FOREGROUND_CHANNEL") .setContentTitle("应用运行中") .setContentText("正在接收通知") .setSmallIcon(R.drawable.ic_notification) .build() startForeground(1, notification) } override fun onBind(intent: Intent?): IBinder? = null }同步未展示的通知
既然用户打开应用能获取到未展示的通知,你可以:- 在应用启动时,调用FCM相关API拉取未接收的消息;
- 或者在服务器端记录已发送但未确认接收的消息,应用启动时请求服务器同步,然后本地展示这些消息。
集成厂商推送通道
国内厂商对自家推送服务优先级更高,比如华为HMS Push、小米Mi Push、OPPO OPush等。你可以在应用中集成这些厂商的推送SDK,检测到对应厂商设备时,优先使用厂商推送通道发送通知,能大幅提升送达率。
内容的提问来源于stack exchange,提问作者Isaac Bosca

