第2章:通知优先级动态计算
2.1 原理概述
Android通知系统的优先级决定了通知的展示位置、是否弹出、是否震动、在状态栏的可见性以及分组行为。动态计算涉及三个核心因素:
- 重要性分级(Importance):系统根据通知渠道(Channel)预设的重要性级别,提供从
IMPORTANCE_NONE到IMPORTANCE_HIGH五个等级。 - 用户偏好覆盖(User Override):用户在系统设置中可单独调整每个应用的通道重要性,甚至完全屏蔽。
- 系统策略权重(System Policy):SystemUI根据当前系统状态(如DND、屏幕开关、通话中)叠加额外权重,影响最终优先级。
最终优先级 = max(用户覆盖后重要性,系统策略强制级别) + 场景偏移量
2.2 实现流程
- 加载渠道配置:从
NotificationChannel对象中读取原始重要性。 - 检查用户覆盖:读取
NotificationManager.Policy中用户修改过的渠道重要性映射。 - 执行系统策略评估:判断DND模式、是否处于通话、屏幕状态等,计算策略权重。
- 动态调整:如果策略权重高于用户配置,则提升优先级;否则保留用户配置。
- 输出最终优先级:将结果注入通知的
importance字段,供SystemUI渲染使用。
用户覆盖适用于长期偏好调整(如“不希望微信弹出”),而系统策略权重针对瞬时场景(如“驾驶模式强行提高导航通知优先级”)。
2.3 核心API与数据结构
| 类/接口 | 作用 | 关键方法/字段 |
|---|---|---|
NotificationChannel | 保存渠道重要性基础值 | getImportance()、setImportance() |
NotificationManager.Policy | 存储用户覆盖规则 | getImportanceOverride() |
Ranking | 系统排序时包含的临时优先级 | getImportance() |
NotificationListenersService.RankingMap | 监听器获取全量排序 | getRanking() |
2.4 代码实现:优先级计算引擎
// PriorityCalculator.java
public class PriorityCalculator {
private final Context context;
private final NotificationManager nm;
public PriorityCalculator(Context context) {
this.context = context;
this.nm = context.getSystemService(NotificationManager.class);
}
public int calculateFinalImportance(NotificationChannel channel, int userId) {
int baseImp = channel.getImportance();
// 步骤1:加载用户覆盖
UserImportanceOverride override = loadUserOverride(channel.getId(), userId);
int afterUser = (override != null) ? override.getImportance() : baseImp;
// 步骤2:系统策略评估
int systemPolicyImp = evaluateSystemPolicy();
// 步骤3:取最大值
int finalImp = Math.max(afterUser, systemPolicyImp);
// 步骤4:边界修正(不允许低于NONE)
return Math.max(finalImp, NotificationManager.IMPORTANCE_NONE);
}
private int evaluateSystemPolicy() {
AudioManager audio = context.getSystemService(AudioManager.class);
boolean dnd = nm.isNotificationPolicyAccessGranted()
&& nm.getCurrentInterruptionFilter()
== NotificationManager.INTERRUPTION_FILTER_PRIORITY;
boolean inCall = audio.getMode() == AudioManager.MODE_IN_CALL;
int policyImp = NotificationManager.IMPORTANCE_DEFAULT;
if (dnd) {
// DND模式下高优先级通知仍然展示
policyImp = NotificationManager.IMPORTANCE_HIGH;
}
if (inCall) {
// 通话中降低非关键通知
policyImp = Math.min(policyImp, NotificationManager.IMPORTANCE_LOW);
}
return policyImp;
}
}
2.5 系统策略权重详细设计
系统策略权重通过PolicyWeight枚举定义,每个策略对应一个重要性偏移量:
| 策略场景 | 偏移值 | 说明 |
|---|---|---|
| 静默模式(DND) | +1 | 仅允许高优先级通知通过 |
| 通话中 | -2 | 降低非通话相关通知的可见性 |
| 屏幕关闭 | +0 | 不额外调整 |
| 车载模式 | +2 | 强制提高导航和紧急通知 |
| 低电量 | -1 | 抑制非关键弹窗 |
注意:策略权重的叠加顺序可能会产生反直觉结果。例如DND期间若同时处于低电量,则DND的+1和低电量的-1相互抵消,最终优先级回落为默认值。实际实现中建议加入优先级保护上限(不得低于IMPORTANCE_MIN)。
2.6 调试与验证
通过adb shell dumpsys notification查看渠道与通知的实时重要性:
# 查看指定包的所有通知渠道
adb shell dumpsys notification --package com.example.app
# 在输出中查找 "Importance:" 字段
Channel: "default" importance=3 (HIGH)
user override: importance=2 (DEFAULT)
system policy: weight=+1 (DND active)
final: 2 (DEFAULT)
日志定位可开启SystemUI的跟踪:
adb shell setprop log.tag.PriorityCalculator VERBOSE
adb logcat -s PriorityCalculator
2.7 常见错误与性能优化
- 用户覆盖不生效:检查是否调用
NotificationManager.setImportanceOverride()后未持久化到PolicyFile。 - 策略权重计算过于频繁:在
onNotificationPosted()回调中每次重新评估全量策略会导致卡顿。建议引入策略缓存,仅在系统状态变更时刷新。 - 多用户下用户覆盖ID混淆:必须将
userId与channelId拼接作为缓存键。
性能优化方案:将策略评估迁移到后台线程,使用
HandlerThread;结果通过LiveData或Flow返回UI线程。2.8 动态优先级计算流程SVG
2.9 部署注意事项
优先级计算逻辑放置在SystemUI的NotificationInterruptStateProvider中最为合适。确保在系统策略变更时(如DND模式切换)重新评估所有可见通知。使用BroadcastReceiver监听TelephonyManager.ACTION_PHONE_STATE_CHANGED和NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED。
推荐将最终重要性存储在
NotificationEntry的临时字段中,并在排序比较器中直接引用,避免每次比较重复计算。