Android 15 核心子系统系列 - 第24篇本篇深入分析Android通知系统的核心服务NotificationManagerService,理解通知渠道、优先级控制和免打扰模式的工作机制。引言想象一下:你的手机一天收到几十上百条通知——即时消息、邮件、新闻推送、应用更新…如果每条通知都同样"吵闹",你会疯掉。这就是为什么Android从8.0开始引入了通知渠道(Notification Channels)机制——让用户精细控制每个应用的不同类型通知。而管理这一切的核心服务,就是NotificationManagerService(NMS)。在上一篇电源管理中,我们看到Android如何限制后台应用;今天我们将看到,Android如何让前台通知既有效又不烦人。一、NotificationManagerService整体架构1.1 架构设计哲学NMS的设计遵循几个核心原则:用户至上:用户的通知偏好优先于应用的发送请求精细控制:通过渠道机制实现类型级别的控制智能排序:根据重要性、时间、用户习惯排序隐私保护:锁屏通知可隐藏敏感内容1.2 四层架构1.3 核心组件// frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.javapublicclassNotificationManagerServiceextendsSystemService{// 核心组件privateRankingHelpermRankingHelper;// 排序助手privateNotificationListenersmListenerManager;// 监听器管理privateZenModeHelpermZenModeHelper;// 免打扰模式privateSnoozeHelpermSnoozeHelper;// 延迟助手privateGroupHelpermGroupHelper;// 分组助手// 通知记录privatefinalNotificationRecordListmNotificationList;// 所有通知privatefinalArrayMapString,NotificationRecordmNotificationsByKey;// 按key索引// 配置privateAtomicFilemPolicyFile;// 策略配置文件privateRankingConfigmRankingConfig;// 排序配置}二、通知渠道(Notification Channels)机制2.1 为什么需要通知渠道?在Android 8.0之前,应用的所有通知共享一个设置,用户只能选择"全开"或"全关"。这导致:想收重要消息,就得忍受垃圾推送不想被打扰,就收不到关键通知通知渠道解决了这个问题——同一应用的不同类型通知,可以分别控制。生活类比:就像邮件的"收件箱分类"——工作邮件设置提醒,营销邮件静音,垃圾邮件直接屏蔽。2.2 渠道的三级层次结构应用 (App) ├─ 渠道组 (Channel Group) - 可选 │ ├─ 渠道1 (Channel) │ └─ 渠道2 (Channel) └─ 渠道3 (Channel) - 未分组示例:即时通讯应用// 创建渠道组valgroupId="message_group"notificationManager.createNotificationChannelGroup(NotificationChannelGroup(groupId,"消息通知"))// 创建渠道valchannels=listOf(NotificationChannel("private_msg","私聊消息",IMPORTANCE_HIGH).apply{group=groupId description="一对一聊天消息"enableVibration(true)setSound(privateMsgSound,audioAttributes)},NotificationChannel("group_msg","群聊消息",IMPORTANCE_DEFAULT).apply{group=groupId description="群组聊天消息"enableVibration(false)// 群消息不震动},NotificationChannel("system_notice","系统通知",IMPORTANCE_LOW).apply{description="账号、安全相关通知"setSound(null,null)// 无声音})notificationManager.createNotificationChannels(channels)2.3 渠道重要性级别Android定义了5个重要性级别:级别值行为使用场景IMPORTANCE_HIGH4🔴 声音+横幅+锁屏来电、闹钟、紧急消息IMPORTANCE_DEFAULT3🟠 声音+状态栏即时消息、邮件IMPORTANCE_LOW2🟡 无声音+状态栏推荐内容、社交更新IMPORTANCE_MIN1🟢 仅通知栏(无图标)后台状态、天气IMPORTANCE_NONE0⚪ 完全屏蔽用户禁用的渠道源码实现:// NotificationChannel.java// 根据重要性决定通知行为booleanshouldShowBadge(){returnmImportance=IMPORTANCE_LOW;// 低及以上显示角标}booleancanShowBanner(){returnmImportance=IMPORTANCE_HIGH;// 仅高级别显示横幅}booleancanBypassDnd(){returnmImportance=IMPORTANCE_HIGHmBypassDnd;// 高级别可绕过免打扰}2.4 渠道的不可逆降级关键设计:用户降低渠道重要性后,应用无法通过代码恢复。// NotificationManagerService.javavoidupdateNotificationChannelInt(Stringpkg,intuid,NotificationChannelchannel,booleanfromTargetApp){NotificationChannelexisting=getChannel(pkg,uid,channel.getId());if(fromTargetApp){// 应用尝试更新渠道// 检查是否试图提升重要性if(channel.getImportance()existing.getImportance()){// 禁止!保持用户设置的较低值channel.setImportance(existing.getImportance());Log.w(TAG,"App cannot increase channel importance");}}// 更新渠道updateChannel(pkg,uid,channel);}设计理念:用户说了算——应用不能推翻用户的选择,这是Android通知系统的核心原则。三、通知发送与处理流程3.1 完整的发送流程App调用NotificationManager.notify() ↓ Binder IPC到NMS ↓ 权限检查(AppOps) ↓ 渠道验证(必须有效渠道) ↓ 重要性评估(RankingHelper) ↓ 免打扰模式过滤(ZenModeHelper) ↓ 创建NotificationRecord ↓ 通知排序(mRankingHelper.sort()) ↓ 通知监听器回调 ↓ 显示到StatusBar3.2 源码分析:enqueueNotificationInternal// NotificationManagerService.javavoidenqueueNotificationInternal(finalStringpkg,finalStringopPkg,finalintcallingUid,finalintcallingPid,finalStringtag,finalintid,finalNotificationnotification,intuserId){// 1. 权限检查checkCallerIsSystemOrSameApp(pkg);finalbooleanisSystemNotification=isUidSystem(callingUid);// 2. 速率限制检查(防刷屏)if(!isSystemNotification){synchronized(mNotificationLock){finalfloatrate=mUsageStats.getAppEnqueueRate(pkg);if(ratemMaxPackageEnqueueRate){// 超过速率限制,延迟发送mSnoozeHelper.snooze(r,SNOOZE_UNTIL_UNTHROTTLED);return;}}}// 3. 获取或创建通知渠道finalNotificationChannelchannel=getChannel(pkg,callingUid,notification.getChannelId());if(channel==null){// Android 8.0+必须指定有效渠道thrownewIllegalArgumentException("No Channel found for pkg="+pkg+", channelId="+notification.getChannelId());}// 4. 创建通知记录finalNotificationRecordr=newNotificationRecord(getContext(),notification,channel);// 5. 检查免打扰模式if(mZenModeHelper.shouldIntercept(r)){r