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

Android应用通知系统问题求助:后台/关闭状态下持久通知不更新、祷告时间名称错误、重复通知及45分钟提醒缺失

Android应用通知系统问题求助:后台/关闭状态下持久通知不更新、祷告时间名称错误、重复通知及45分钟提醒缺失

嘿,我仔细看了你的问题和代码,这几个都是Android后台任务与通知调度里的常见坑,咱们一个个拆解解决:


一、后台/关闭状态下持久通知停止更新

问题根源

你当前用的handler.postDelayed依赖App进程的Looper,一旦App被手动关闭或系统回收,定时器直接失效。虽然用了ForegroundService,但系统资源紧张时仍可能杀掉它;而且WorkManager的15分钟更新逻辑没和通知更新绑定,导致倒计时停摆。

解决方案

  1. 用WorkManager实现稳定的倒计时更新
    把倒计时更新逻辑放到CoroutineWorker里,设置每分钟执行一次(可按需调整),每次执行时计算剩余时间并更新通知——WorkManager即使App关闭也能在后台触发(可引导用户关闭电池优化进一步提升稳定性):
    class CountdownUpdateWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
        override suspend fun doWork(): Result {
            val prefs = applicationContext.getSharedPreferences("PrayerPrefs", Context.MODE_PRIVATE)
            val nextPrayerTime = prefs.getLong("nextPrayerTime", 0)
            val currentPrayerName = prefs.getString("currentPrayerName", "Namaz")
            
            if (nextPrayerTime > System.currentTimeMillis()) {
                val timeRemaining = nextPrayerTime - System.currentTimeMillis()
                val hours = (timeRemaining / (1000 * 60 * 60)).toInt()
                val minutes = ((timeRemaining % (1000 * 60 * 60)) / (1000 * 60)).toInt()
                val formattedTime = String.format("%02d:%02d", hours, minutes)
                NotificationHelper(applicationContext).updatePersistentNotification(currentPrayerName, formattedTime)
            }
            return Result.success()
        }
    }
    
    启动时调度任务:
    val updateRequest = PeriodicWorkRequestBuilder<CountdownUpdateWorker>(1, TimeUnit.MINUTES)
        .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.NOT_REQUIRED).build())
        .build()
    WorkManager.getInstance(context).enqueueUniquePeriodicWork(
        "CountdownUpdate", 
        ExistingPeriodicWorkPolicy.REPLACE, 
        updateRequest
    )
    
  2. 强化ForegroundService稳定性
    在Service的onStartCommand返回START_STICKY,让系统杀掉后自动重启;Android 12+务必申请POST_NOTIFICATIONS权限,确保通知能正常显示。

二、祷告时间名称显示错误

问题根源

currentPrayerName依赖全局变量,异步获取新祷告时间时可能未及时更新就触发通知;同时Alarm调度时没传递正确的祷告名称,BroadcastReceiver只能拿到旧值。

解决方案

  1. 同步祷告信息到SharedPreferences
    获取到新的祷告时间和名称后,立即存入SharedPreferences,确保所有组件(Service、Worker、Receiver)都能拿到最新数据:
    fun fetchLocationAndPrayerTimes() {
        // 假设已获取到nextPrayerTime和newPrayerName
        val prefs = context.getSharedPreferences("PrayerPrefs", Context.MODE_PRIVATE)
        with(prefs.edit()) {
            putLong("nextPrayerTime", nextPrayerTime)
            putString("currentPrayerName", newPrayerName)
            apply()
        }
        NotificationHelper(context).schedulePrayerNotifications(nextPrayerTime, newPrayerName)
    }
    
  2. 在AlarmIntent中传递祷告名称
    调度Alarm时把名称放到Intent Extra里,避免依赖全局变量:
    // 修改schedulePrayerNotifications方法,新增prayerName参数
    val prayerIntent = PendingIntent.getBroadcast(context, 2, Intent(context, PrayerBroadcastReceiver::class.java).apply {
        action = "PRAYER_NOTIFICATION"
        putExtra("PRAYER_NAME", prayerName)
    }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
    
    然后在BroadcastReceiver中读取:
    override fun onReceive(context: Context?, intent: Intent?) {
        intent?.let {
            when(it.action) {
                "PRAYER_NOTIFICATION" -> {
                    val prayerName = it.getStringExtra("PRAYER_NAME") ?: "Namaz"
                    NotificationHelper(context!!).sendPrayerNotification(prayerName)
                }
                // 处理其他action
            }
        }
    }
    

三、祷告时间重复发送通知

问题根源

仅用祷告名称判断是否发送过通知,第二天同一祷告会被拦截;同时startTimer中的判断逻辑有误差,可能多次进入发送分支。

解决方案

  1. 保存最后发送通知的时间戳而非名称
    用时间戳判断是否重复发送(允许5分钟误差,避免系统时间偏差):
    // 修改sendPrayerNotification方法
    fun sendPrayerNotification(prayerName: String, prayerTime: Long) {
        val prefs = context.getSharedPreferences("NotificationPrefs", Context.MODE_PRIVATE)
        val lastSentTime = prefs.getLong("lastSentPrayerTime", 0)
        
        if (Math.abs(prayerTime - lastSentTime) < 5 * 60 * 1000) {
            Log.d("NotificationHelper", "$prayerName 通知已发送过")
            return
        }
        
        // 发送通知逻辑...
        
        with(prefs.edit()) {
            putLong("lastSentPrayerTime", prayerTime)
            apply()
        }
    }
    
  2. 优化startTimer的结束逻辑
    触发通知后立即移除当前Runnable,避免重复执行:
    override fun run() {
        val currentTime = System.currentTimeMillis()
        val timeRemaining = nextPrayerTime - currentTime
    
        if (timeRemaining > 0) {
            // 更新倒计时和通知
            handler.postDelayed(this, 1000)
        } else {
            currentPrayerName?.let { name ->
                val prefs = context.getSharedPreferences("NotificationPrefs", Context.MODE_PRIVATE)
                val lastSentTime = prefs.getLong("lastSentPrayerTime", 0)
                if (Math.abs(nextPrayerTime - lastSentTime) > 5 * 60 * 1000) {
                    notificationHelper.sendPrayerNotification(name, nextPrayerTime)
                    Snackbar.make(binding.root, "${name} vakti geldi!", Snackbar.LENGTH_LONG).show()
                    prefs.edit().putLong("lastSentPrayerTime", nextPrayerTime).apply()
                }
            }
            fetchLocationAndPrayerTimes()
            handler.removeCallbacks(this)
        }
    }
    

四、45分钟提醒通知缺失

问题根源

固定的PendingIntent请求码会导致新的Alarm覆盖旧的;同时全局的hasSentPriorNotification标记发送一次后不会重置,下一个祷告的提醒被拦截。

解决方案

  1. 使用唯一的PendingIntent请求码
    用祷告时间作为请求码,避免Alarm被覆盖:
    // 修改schedulePrayerNotifications方法
    val priorRequestCode = nextPrayerTime.toInt()
    val priorIntent = PendingIntent.getBroadcast(context, priorRequestCode, Intent(context, PrayerBroadcastReceiver::class.java).apply {
        action = "PRIOR_NOTIFICATION"
        putExtra("PRAYER_NAME", prayerName)
        putExtra("PRAYER_TIME", nextPrayerTime)
    }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
    
  2. 针对每个祷告时间重置提醒标记
    用祷告时间作为标记的key,避免全局拦截:
    fun sendPriorNotification(prayerTime: Long, prayerName: String) {
        val prefs = context.getSharedPreferences("NotificationPrefs", Context.MODE_PRIVATE)
        val hasSent = prefs.getBoolean("priorSent_$prayerTime", false)
    
        if (!hasSent) {
            // 发送提醒通知逻辑...
            with(prefs.edit()) {
                putBoolean("priorSent_$prayerTime", true)
                // 一天后清除标记,避免SharedPreferences膨胀
                putLong("priorSent_$prayerTime_expiry", System.currentTimeMillis() + 24 * 60 * 60 * 1000)
                apply()
            }
        }
    }
    

额外调试建议

  • 引导用户将App加入电池优化白名单,避免Doze模式限制后台任务;
  • 用adb shell am force-stop com.example.esmaulhusna命令杀死进程,测试后台场景;
  • 在关键逻辑处添加详细日志,比如WorkManager执行、Alarm触发、通知发送环节,方便排查问题。

备注:内容来源于stack exchange,提问作者Speed Master

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.16 03:13:00