第2章:通知优先级动态计算

2.1 原理概述

Android通知系统的优先级决定了通知的展示位置、是否弹出、是否震动、在状态栏的可见性以及分组行为。动态计算涉及三个核心因素:

  • 重要性分级(Importance):系统根据通知渠道(Channel)预设的重要性级别,提供从IMPORTANCE_NONE到IMPORTANCE_HIGH五个等级。
  • 用户偏好覆盖(User Override):用户在系统设置中可单独调整每个应用的通道重要性,甚至完全屏蔽。
  • 系统策略权重(System Policy):SystemUI根据当前系统状态(如DND、屏幕开关、通话中)叠加额外权重,影响最终优先级。
最终优先级 = max(用户覆盖后重要性,系统策略强制级别) + 场景偏移量

2.2 实现流程

  1. 加载渠道配置:从NotificationChannel对象中读取原始重要性。
  2. 检查用户覆盖:读取NotificationManager.Policy中用户修改过的渠道重要性映射。
  3. 执行系统策略评估:判断DND模式、是否处于通话、屏幕状态等,计算策略权重。
  4. 动态调整:如果策略权重高于用户配置,则提升优先级;否则保留用户配置。
  5. 输出最终优先级:将结果注入通知的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

通知优先级动态计算流程 通知优先级动态计算流程 渠道重要性(Base) 用户偏好覆盖(Override) 覆盖有效? 使用覆盖值 使用基础值 系统策略权重评估 DND / 通话 / 车载 / 低电量 max(afterUser, policy) 最终重要性 说明:中间状态需缓存

2.9 部署注意事项

优先级计算逻辑放置在SystemUI的NotificationInterruptStateProvider中最为合适。确保在系统策略变更时(如DND模式切换)重新评估所有可见通知。使用BroadcastReceiver监听TelephonyManager.ACTION_PHONE_STATE_CHANGED和NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED。

推荐将最终重要性存储在NotificationEntry的临时字段中,并在排序比较器中直接引用,避免每次比较重复计算。