AOSP 通知排序代码分析

目标

分析AOSP中关于通知排序的代码,了解Android通知排序机制。

分析

NotificationRecord排序的代码位于NotificationComparator:

// frameworks/base/services/core/java/com/android/server/notification/NotificationComparator.java
@Override
public int compare(NotificationRecord left, NotificationRecord right) {
    final int leftImportance = left.getImportance();
    final int rightImportance = right.getImportance();
    final boolean isLeftHighImportance = leftImportance >= IMPORTANCE_DEFAULT;
    final boolean isRightHighImportance = rightImportance >= IMPORTANCE_DEFAULT;
    if (isLeftHighImportance != isRightHighImportance) {
        // by importance bucket, high importance higher than low importance
        return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance);
    }

    // If a score has been assigned by notification assistant service, use this service
    // rank results within each bucket instead of this comparator implementation.
    if (left.getRankingScore() != right.getRankingScore()) {
        return -1 * Float.compare(left.getRankingScore(), right.getRankingScore());
    }

    // first all colorized notifications
    boolean leftImportantColorized = isImportantColorized(left);
    boolean rightImportantColorized = isImportantColorized(right);
    if (leftImportantColorized != rightImportantColorized) {
        return -1 * Boolean.compare(leftImportantColorized, rightImportantColorized);
    }

    // sufficiently important ongoing notifications of certain categories
    boolean leftImportantOngoing = isImportantOngoing(left);
    boolean rightImportantOngoing = isImportantOngoing(right);
    if (leftImportantOngoing != rightImportantOngoing) {
        // by ongoing, ongoing higher than non-ongoing
        return -1 * Boolean.compare(leftImportantOngoing, rightImportantOngoing);
    }

    boolean leftMessaging = isImportantMessaging(left);
    boolean rightMessaging = isImportantMessaging(right);
    if (leftMessaging != rightMessaging) {
        return -1 * Boolean.compare(leftMessaging, rightMessaging);
    }

    // Next: sufficiently import person to person communication
    boolean leftPeople = isImportantPeople(left);
    boolean rightPeople = isImportantPeople(right);
    final int contactAffinityComparison =
            Float.compare(left.getContactAffinity(), right.getContactAffinity());

    if (leftPeople && rightPeople){
        // by contact proximity, close to far. if same proximity, check further fields.
        if (contactAffinityComparison != 0) {
            return -1 * contactAffinityComparison;
        }
    } else if (leftPeople != rightPeople) {
        // People, messaging higher than non-messaging
        return -1 * Boolean.compare(leftPeople, rightPeople);
    }

    boolean leftSystemMax = isSystemMax(left);
    boolean rightSystemMax = isSystemMax(right);
    if (leftSystemMax != rightSystemMax) {
        return -1 * Boolean.compare(leftSystemMax, rightSystemMax);
    }
    if (leftImportance != rightImportance) {
        // by importance, high to low
        return -1 * Integer.compare(leftImportance, rightImportance);
    }

    // by contact proximity, close to far. if same proximity, check further fields.
    if (contactAffinityComparison != 0) {
        return -1 * contactAffinityComparison;
    }

    // Whether or not the notification can bypass DND.
    final int leftPackagePriority = left.getPackagePriority();
    final int rightPackagePriority = right.getPackagePriority();
    if (leftPackagePriority != rightPackagePriority) {
        // by priority, high to low
        return -1 * Integer.compare(leftPackagePriority, rightPackagePriority);
    }

    final int leftPriority = left.getSbn().getNotification().priority;
    final int rightPriority = right.getSbn().getNotification().priority;
    if (leftPriority != rightPriority) {
        // by priority, high to low
        return -1 * Integer.compare(leftPriority, rightPriority);
    }

    // then break ties by time, most recent first
    return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
}

每个判断条件的解释如下:

  1. importance:如果一个大于等于default,一个小于default,则按importance排序;
  2. getRankingScore:这个从applyAdjustments中过来,应用不能控制;
  3. isImportantColorized:这个需要android.permission.USE_COLORIZED_NOTIFICATIONS权限,普通应用无法申请;
  4. isImportantOngoing:应用可配置;
  5. isImportantMessaging:这个必须满足isDefaultMessagingApp,即系统默认短信应用,需用户主动配置;
  6. isImportantPeople:和通讯录设置有关,非应用干预;
  7. isSystemMax:包名必须是android或者com.android.systemui;
  8. getPackagePriority:取决于canBypassDnd,即该应用是否可绕过Do Not Disturb;
  9. priority:应用可配置;
  10. getRankingTimeMs:时间,可由开发者决定但是不能超过通知发出的时间,如下所示。

提升通知优先级

通知发出时间

一种比较流行的方法是设置通知时间为未来时间:

public void postNotificationTop() {
    NotificationManager notificationManager = (NotificationManager)
            getSystemService(Context.NOTIFICATION_SERVICE);
    NotificationChannel channel = new NotificationChannel("channel_id",
            "channel_name", NotificationManager.IMPORTANCE_HIGH);
    notificationManager.createNotificationChannel(channel);
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channel_id")
            .setContentTitle("Title")
            .setContentText("text")
            .setSmallIcon(R.mipmap.ic_launcher)
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setWhen(System.currentTimeMillis() + 10000)
            .setShowWhen(false)
            .setAutoCancel(true);
    notificationManager.notify(0, builder.build());
}

setWhen设置时间为当前时间+10000毫秒,然后setShowWhen为false。然而实际上这样设置并不起作用,因为代码中判断了不能超过通知发出时间,所以实际还是通知优先级决定了排序。

// frameworks/base/services/core/java/com/android/server/notification/NotificationRecord.java
/**
 * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
 *     of the previous notification record, 0 otherwise
 */
private long calculateRankingTimeMs(long previousRankingTimeMs) {
    Notification n = getNotification();
    // Take developer provided 'when', unless it's in the future.
    if (n.when != 0 && n.when <= getSbn().getPostTime()) {
        return n.when;
    }
    // If we've ranked a previous instance with a timestamp, inherit it. This case is
    // important in order to have ranking stability for updating notifications.
    if (previousRankingTimeMs > 0) {
        return previousRankingTimeMs;
    }
    return getSbn().getPostTime();
}

Ongoing通知

Ongoing的方式是可以的,配置setOngoing(true)即可:

public void postNotificationTop() {
    NotificationManager notificationManager = (NotificationManager)
            getSystemService(Context.NOTIFICATION_SERVICE);
    NotificationChannel channel = new NotificationChannel("capcut_top",
            "capcut top", NotificationManager.IMPORTANCE_HIGH);
    notificationManager.createNotificationChannel(channel);
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "capcut_top")
            .setContentTitle("Capcut")
            .setContentText("置顶消息测试")
            .setSmallIcon(R.mipmap.ic_launcher)
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setOngoing(true)
            .setShowWhen(false)
            .setAutoCancel(true);
    notificationManager.notify(0, builder.build());
}

配置mBypassDnd=true

在getPackagePriority的比较中,实际比较的是mPackagePriority:

public int getPackagePriority() {
    return mPackagePriority;
}

而setPackagePriority又是决定于NotificationChannel的canBypassDnd:

public RankingReconsideration process(NotificationRecord record) {
    if (record == null || record.getNotification() == null) {
        if (DBG) Slog.d(TAG, "skipping empty notification");
        return null;
    }
    if (mConfig == null) {
        if (DBG) Slog.d(TAG, "missing config");
        return null;
    }
    record.setPackagePriority(record.getChannel().canBypassDnd()
            ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT);
    return null;
}

最后canBypassDnd是决定于mBypassDnd变量:

/**
 * Whether or not notifications posted to this channel can bypass the Do Not Disturb
 * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode.
 */
public boolean canBypassDnd() {
    return mBypassDnd;
}

可以通过setBypassDnd来设置

channel.setBypassDnd(true);