如何在前台服务中更新通知内容(如播放进度)?

解读

国内面试中,这道题表面问“通知刷新”,实则考察对前台服务生命周期、通知通道、内存可见性、主线程阻塞、系统 ANR 阈值以及厂商 ROM 差异的综合理解。面试官希望听到:

  1. 为什么用前台服务而不是后台服务;
  2. 如何合法地把通知 ID 复用并保证 UI 实时;
  3. 在折叠屏、小窗、通知栏折叠、锁屏、车载场景下如何保证进度条、按钮状态一致;
  4. 如何兼容国内 ROM(MIUI、ColorOS、EMUI)对高频更新通知的节流限制;
  5. 如何防止高频刷新导致主线程 GC 抖动或耗电异常,从而被系统或应用商店判定为“异常耗电”。

知识点

  1. 前台服务必须调用 startForeground(int id, Notification),且通知 ID 在进程生命周期内保持唯一;复用同一 ID 即可原地更新,无需再次调用 startForeground
  2. 通知内容通过 NotificationCompat.Builder 重建,然后 NotificationManager.notify(id, newNotification);系统会自动替换旧通知,不会闪烁。
  3. 媒体场景优先使用 MediaStyle + MediaSession,系统会接管锁屏、蓝牙、车载桌面卡片,减少主动刷新次数。
  4. 高频进度条(秒级)应使用 setProgress(max, progress, false),但国内 ROM 对 500 ms 以内刷新会做节流,建议 1 s 一次,并在用户拖动时暂停刷新。
  5. 更新动作必须在主线程以外完成,但 NotificationManager.notify 必须在主线程调用,否则部分 ROM 直接抛 BadNotificationException
  6. 若更新间隔 < 1 s,建议采用“时间窗口聚合”策略:在子线程记录最新进度,主线程 Handler 每 1 s 批量刷新一次,兼顾实时性与功耗。
  7. 折叠屏/小窗/车载 ROM 可能隐藏进度条,需通过 MediaSession.setPlaybackState 同步,确保系统 UI 统一。
  8. 耗电监控:国内商店后台会统计 NotificationManager.notify 调用频次,超过 60 次/分钟会被标记“异常”,需埋点监控线上数据。
  9. 适配 Android 13 运行时通知权限:若用户拒绝,前台服务仍会显示通知但无内容,需降级为常驻图标,避免 ANR。
  10. 杀死复活:服务被系统回收后,再次 startService 会走到 onStartCommand,此时需重建 MediaSession 并恢复进度,否则通知按钮点击失效。

答案

  1. onStartCommand 中首次构建通知并调用 startForeground(ID, notification),立即将服务提为前台优先级。
  2. 维护一个 NotificationCompat.Builder 实例,在子线程中每 1 s 计算最新播放进度,通过主线程 Handler post 到主线程:
    builder.setProgress(duration, current, false)
           .setContentText("${current/60}:${current%60}")
    nm.notify(ID, builder.build())
    
  3. 使用同一通知 ID,系统原地更新,不会重新弹出;若需改变按钮状态(播放/暂停),只需重新 addAction 并再次 notify
  4. 同时向 MediaSession 设置 PlaybackState,让系统锁屏、车载、蓝牙同步进度,减少主动刷新压力。
  5. onDestroy 中调用 stopForeground(true)mediaSession.release(),防止内存泄漏与厂商 ROM 报“服务未释放”异常。
  6. 线上通过 BatteryHistorian 与自有埋点监控 notify 频次,超过 60 次/分钟自动降级到 2 s 刷新,确保不被应用商店下架。

拓展思考

  1. 如果业务要求毫秒级波形进度,可放弃通知进度条,改为在悬浮窗或画中画(PiP)中自绘,通知仅保留静态按钮,彻底绕过 ROM 节流。
  2. 对于车载 Android Automotive OS,通知进度条不会显示,需通过 CarAppService 提交 ProgressTemplate,由车载桌面渲染,手机侧通知仅作为备用。
  3. 当用户关闭“前台服务通知”权限(Android 13 新增),系统会隐藏通知但服务仍运行,此时需注册 NotificationListenerService 监听系统是否成功展示,若失败则降级为常驻图标并记录埋点,防止用户投诉“进度消失”。
  4. 高版本系统对“精确闹钟+高频通知”组合限制极严,若播放进度依赖 AlarmManager 定时刷新,需申请 USE_EXACT_ALARM 权限并在 manifest 中声明合理场景,否则国内商店审核会被驳回。
  5. 未来 Android 14 引入“前台服务类型必须匹配清单声明”的强制策略,媒体播放必须声明 mediaPlayback 类型,否则系统抛出 ForegroundServiceStartNotAllowedException,需在编译期通过 annotationProcessor 做静态检查,避免线上崩溃。