1.在syatemUI显示相关通知之前,做了很多的准备工作,这边我们来说说notification的排序。
排序工作主要是在NotificationManagerService.java里面实现的。
路径:frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
我们来看下enqueueNotificationWithTag方法,这里调用了enqueueNotificationInternal方法,这里就是通知处理的核心了。
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId) {
if (DBG) {
Slog.v(TAG, “enqueueNotificationInternal: pkg=” + pkg + ” id=” + id
+ ” notification=” + notification);
}
checkCallerIsSystemOrSameApp(pkg);
// 校验UID
final boolean isSystemNotification = isUidSystem(callingUid) || (“android”.equals(pkg));
final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);
final int userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, incomingUserId, true, false, “enqueueNotification”, pkg);
final UserHandle user = new UserHandle(userId);
// Fix the notification as best we can.
try {
final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
(userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, userId, notification);
} catch (NameNotFoundException e) {
Slog.e(TAG, “Cannot create a context for sending app”, e);
return;
}
mUsageStats.registerEnqueuedByApp(pkg);
// Limit the number of notifications that any given package except the android
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
// 这里会做一个限制,除了系统级别的应用之外,其他应用的notification数量会做限制,用来防止DOS攻击导致的泄露
if (!isSystemNotification && !isNotificationFromListener) {
synchronized (mNotificationList) {
final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
if (appEnqueueRate > mMaxPackageEnqueueRate) {
mUsageStats.registerOverRateQuota(pkg);
final long now = SystemClock.elapsedRealtime();
if ((now – mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
Slog.e(TAG, “Package enqueue rate is ” + appEnqueueRate
+ “. Shedding events. package=” + pkg);
mLastOverRateLogTime = now;
}
return;
}
int count = 0;
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
final NotificationRecord r = mNotificationList.get(i);
if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) {
if (r.sbn.getId() == id && TextUtils.equals(r.sbn.getTag(), tag)) {
break; // Allow updating existing notification
}
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {// 同一个应用发送notification数量不能超过50
mUsageStats.registerOverCountQuota(pkg);
Slog.e(TAG, “Package has already posted ” + count
+ ” notifications. Not showing more. package=” + pkg);
return;
}
}
}
}
}
if (pkg == null || notification == null) {//通知不能为空
throw new IllegalArgumentException(“null not allowed: pkg=” + pkg
+ ” id=” + id + ” notification=” + notification);
}
// Whitelist pending intents.
if (notification.allPendingIntents != null) {
final int intentCount = notification.allPendingIntents.size();
if (intentCount > 0) {
final ActivityManagerInternal am = LocalServices
.getService(ActivityManagerInternal.class);
final long duration = LocalServices.getService(
DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();
for (int i = 0; i < intentCount; i++) {
PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
if (pendingIntent != null) {
am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration);
}
}
}
}
// Sanitize inputs
notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
Notification.PRIORITY_MAX);
// setup local book-keeping
// 验证完条件后,将前面传递进来的Notification封装成一个StatusBarNotification对象,
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,
user);
// 封装NotificationRecord对象
final NotificationRecord r = new NotificationRecord(getContext(), n);
mHandler.post(new EnqueueNotificationRunnable(userId, r));
idOut[0] = id;
}
开启线程,异步处理。
private class EnqueueNotificationRunnable implements Runnable {
private final NotificationRecord r;
private final int userId;
EnqueueNotificationRunnable(int userId, NotificationRecord r) {
this.userId = userId;
this.r = r;
};
@Override
public void run() {
synchronized (mNotificationList) {
final StatusBarNotification n = r.sbn;
if (DBG) Slog.d(TAG, “EnqueueNotificationRunnable.run for: ” + n.getKey());
NotificationRecord old = mNotificationsByKey.get(n.getKey());
if (old != null) {
// Retain ranking information from previous record
r.copyRankingInformation(old);
}
final int callingUid = n.getUid();
final int callingPid = n.getInitialPid();
final Notification notification = n.getNotification();
final String pkg = n.getPackageName();
final int id = n.getId();
final String tag = n.getTag();
final boolean isSystemNotification = isUidSystem(callingUid) ||
(“android”.equals(pkg));
// Handle grouped notifications and bail out early if we
// can to avoid extracting signals.
handleGroupedNotificationLocked(r, old, callingUid, callingPid);
// This conditional is a dirty hack to limit the logging done on
// behalf of the download manager without affecting other apps.
if (!pkg.equals(“com.android.providers.downloads”)
|| Log.isLoggable(“DownloadManager”, Log.VERBOSE)) {
int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
if (old != null) {
enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
}
EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
pkg, id, tag, userId, notification.toString(),
enqueueStatus);
}
mRankingHelper.extractSignals(r);
final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
// blocked apps 判断pkg是否可以显示通知
if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
|| !noteNotificationOp(pkg, callingUid) || isPackageSuspended) {
if (!isSystemNotification) {//不拦截系统通知
if (isPackageSuspended) {
Slog.e(TAG, “Suppressing notification from package due to package ”
+ “suspended by administrator.”);
mUsageStats.registerSuspendedByAdmin(r);
} else {
Slog.e(TAG, “Suppressing notification from package by user request.”);
mUsageStats.registerBlocked(r);
}
return;
}
}
// tell the ranker service about the notification
if (mRankerServices.isEnabled()) {
mRankerServices.onNotificationEnqueued(r);
// TODO delay the code below here for 100ms or until there is an answer
}
// 获取是否已经发送过此notification
int index = indexOfNotificationLocked(n.getKey());
if (index < 0) {
// 如果是新发送的notification,就走新增流程.
mNotificationList.add(r);
mUsageStats.registerPostedByApp(r);
} else {
//如果有发送过,就获取oldNtificationRecord,后面走更新流程 mStatusBar.updateNotification(r.statusBarKey, n)
old = mNotificationList.get(index);
mNotificationList.set(index, r);
mUsageStats.registerUpdatedByApp(r, old);
// Make sure we don’t lose the foreground service state.
notification.flags |=
old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
r.isUpdate = true;
}
Slog.d(TAG, “NotificationRecord, r = “+r);
mNotificationsByKey.put(n.getKey(), r);
// Ensure if this is a foreground service that the proper additional
// flags are set.
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
notification.flags |= Notification.FLAG_ONGOING_EVENT
| Notification.FLAG_NO_CLEAR;
}
applyZenModeLocked(r);
mRankingHelper.sort(mNotificationList);//将mNotificationList排序
// 如果notification设置了smallIcon,调用所有NotificationListeners的notifyPostedLocked方法,
// 通知有新的notification,传入的参数为上面封装成的StatusBarNotification对象.
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
mListeners.notifyPostedLocked(n, oldSbn);
} else {
Slog.e(TAG, “Not posting notification without small icon: ” + notification);
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(n);
}
// ATTENTION: in a future release we will bail out here
// so that we do not play sounds, show lights, etc. for invalid
// notifications
Slog.e(TAG, “WARNING: In a future release this will crash the app: ”
+ n.getPackageName());
}
// buzzBeepBlinkLocked方法负责对消息进行处理。
// 通知status bar显示该notification,确认是否需要声音,震动和闪光,如果需要,那么就发出声音,震动和闪光
buzzBeepBlinkLocked(r);
}
}
}
排序工作在mRankingHelper.sort(mNotificationList)中完成,跟进去看下
路径:frameworks/base/services/core/java/com/android/server/notification/RankingHelper.java
–》查看sort()方法 public void sort(ArrayList<NotificationRecord> notificationList) { … // rank each record individually Collections.sort(notificationList, mPreliminaryComparator);
synchronized (mProxyByGroupTmp) { // record individual ranking result and nominate proxies for each group for (int i = N – 1; i >= 0; i–) { final NotificationRecord record = notificationList.get(i); record.setAuthoritativeRank(i); final String groupKey = record.getGroupKey(); boolean isGroupSummary = record.getNotification().isGroupSummary(); if (isGroupSummary || !mProxyByGroupTmp.containsKey(groupKey)) { mProxyByGroupTmp.put(groupKey, record); } } // assign global sort key: // is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank for (int i = 0; i < N; i++) { final NotificationRecord record = notificationList.get(i); NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey()); String groupSortKey = record.getNotification().getSortKey();
// We need to make sure the developer provided group sort key (gsk) is handled // correctly: // gsk=”” < gsk=non-null-string < gsk=null // // We enforce this by using different prefixes for these three cases. String groupSortKeyPortion; if (groupSortKey == null) { groupSortKeyPortion = “nsk”; } else if (groupSortKey.equals(“”)) { groupSortKeyPortion = “esk”; } else { groupSortKeyPortion = “gsk=” + groupSortKey; }
boolean isGroupSummary = record.getNotification().isGroupSummary(); record.setGlobalSortKey( String.format(“intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x”, record.isRecentlyIntrusive() ? ‘0’ : ‘1’, groupProxy.getAuthoritativeRank(), isGroupSummary ? ‘0’ : ‘1’, groupSortKeyPortion, record.getAuthoritativeRank())); } mProxyByGroupTmp.clear(); }
// Do a second ranking pass, using group proxies Collections.sort(notificationList, mFinalComparator); }
–》查看Collections.sort(notificationList, mPreliminaryComparator); 重点在于比较器mPreliminaryComparator,我们来看下代码 路径:frameworks/base/services/core/java/com/android/server/notification/NotificationComparator.java 进到里面的compare()方法来查看,以下就是具体的排序规则 public int compare(NotificationRecord left, NotificationRecord right) { final int leftImportance = left.getImportance(); final int rightImportance = right.getImportance(); if (leftImportance != rightImportance) { // by importance, high to low return -1 * Integer.compare(leftImportance, rightImportance); }
// 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.sbn.getNotification().priority; final int rightPriority = right.sbn.getNotification().priority; if (leftPriority != rightPriority) { // by priority, high to low return -1 * Integer.compare(leftPriority, rightPriority); }
final float leftPeople = left.getContactAffinity(); final float rightPeople = right.getContactAffinity(); if (leftPeople != rightPeople) { // by contact proximity, close to far return -1 * Float.compare(leftPeople, rightPeople); }
// then break ties by time, most recent first return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs()); } 可以看到,比较器根据leftImportance ,leftPackagePriority ,leftPriority ,leftPeople 这四个参数做出排序,工作基本上算完成了。