不积跬步无以至千里
最近被提了一个关于通知栏上通知排序的bug,之前就想过我们的通知栏上的顺序是在哪进行排序的?其实为什么呢?因为浸提那应用的同事提了一个设置Setpriority(int value)的方法,结果设置优先级较大了,还是没有排到通知队列的前边,很纳闷,今天就看一下咋回事。
其实通知由NotificationManager创建,然后通过IPC传到了NotificationManagerService里面,如图
NotificationManager.java的notify方法
其中核心实现是在调用notifyAsUser方法中,如下图:
如图可知,其中的调用的就是NotificationManagerService中的enqueueNotificationWithTag方法。
其中创建的逻辑咱们就不深究了,咱们看一下关于frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java,它是通知逻辑的真正实现者。
如下图,
看见了binder就看到了对应着实现者的实现逻辑,而我们的关于通知的处理逻辑是在这个内名内部类中,然后我们看下在NotificationManager中调用的方法,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); } /// M:仅仅是过滤一些特殊的通知流,正常的可以忽略{ boolean foundTarget = false; if (pkg != null && pkg.contains(".stub") && notification != null) { String contentTitle = notification.extras != null ? notification.extras.getString(Notification.EXTRA_TITLE) : " "; if (contentTitle != null && contentTitle.startsWith("notify#")) { foundTarget = true; Slog.d(TAG, "enqueueNotification, found notification, callingUid: " + callingUid + ", callingPid: " + callingPid + ", pkg: " + pkg + ", id: " + id + ", tag: " + tag); } } /// @} //检测是否为Phone进程或者系统进程,是否为同一个uid发送, checkCallerIsSystemOrSameApp(pkg); //是否为系统通知 final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg)); //是否为NotificationListenerService监听 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); if (pkg == null || notification == null) { throw new IllegalArgumentException("null not allowed: pkg=" + pkg + " id=" + id + " notification=" + notification); } //创建一个StatusBarNotification对象 final StatusBarNotification n = new StatusBarNotification( pkg, opPkg, id, tag, callingUid, callingPid, 0, notification, user); //这里系统限制了每个应用的发送通知的数量,来阻止DOS攻击防止泄露,这块Toast也有这块的处理会对一个应用发送的条数做处理 if (!isSystemNotification && !isNotificationFromListener) { synchronized (mNotificationList) { //判断是否为一个新的通知还是更新 if(mNotificationsByKey.get(n.getKey()) != null) { // this is an update, rate limit updates only 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; //如果是已存在的通知,是更新操作直接跳出 } count++; //当一个应用的通知数量大于限制数量值时报错,单应用最大数量为50 if (count >= MAX_PACKAGE_NOTIFICATIONS) { mUsageStats.registerOverCountQuota(pkg); Slog.e(TAG, "Package has already posted " + count + " notifications. Not showing more. package=" + pkg); return; } } } } } // 白名单的延迟意图 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); } } } } // 过滤容错处理是否为优先级输入有问题 notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX); // 创建NotificationRecord final NotificationRecord r = new NotificationRecord(getContext(), n); //创建Runnable,后边操作在这个Runnable中 mHandler.post(new EnqueueNotificationRunnable(userId, r)); idOut[0] = id; /// 过滤前边处理的特殊的通知,普通的通知不用考虑 @{ if (foundTarget) { try { Thread.sleep(1000); } catch (InterruptedException exception) { // ignore it. } } /// @} }
总结上边代码,主要有个过滤的特殊通知处理,创建了StatusBarNotification、NotificationRecord对象,并防止DOS攻击对单个应用的通知条数做了限制。
在这里想简单说一下NotificationRecord,因为他这里面有关于对排序有关priority属性的转变
NotificationRecord.java
这是NotificationRecord的构造方法其中后边用的比较多的是mRankingTimeMs和mImportance,而其中mImportance就是对Notification中priority的转变处理,接下来看一下方法
defaultImportance()
这里就是对Notification的priority的转换,还有一些情况比如flags和HIGH_PRIORITY为IMPORTANCE_MAX,通知fullScreenIntent不为空则也设置为IMPORTANCE_MAX
这样操作逻辑又到了这个EnqueueNotificationRunnable中,接着看一下这个内部类
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()); //根据key值获取Old NotificationRecord NotificationRecord old = mNotificationsByKey.get(n.getKey()); if (old != null) { // 获取ranking 信息从old NotificationRecord 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 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 } int index = indexOfNotificationLocked(n.getKey()); if (index < 0) { mNotificationList.add(r); mUsageStats.registerPostedByApp(r); } else { 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; } 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);如上代码,我们这句代码实际为咱们文章的重心,对这些通知的一个排序操作
mRankingHelper.sort(mNotificationList)
代码如下RankingHelper.java 的sort方法
其实主要的是其中排序操作为如下代码,后边为对设置setGroup属性的分组处理。
Collections.sort(notificationList,mPreliminaryComparator);
其中是对通知数据列表设置一个比较器即mPreliminaryComparator进行排序操作,因此我们查看一下这个比较器
NotificationComparator.java
根据这个类可知他会对通知的属性mImportance属性进行比较(就是前边NotificationRecord通过Notification的priority的属性转变来的属性值),然后就是关于PackagePriority进行对比,然后就是通知设置的属性priority,然后就是ContactAffinity属性就行对比,然后就是对mRankingTimeMs属性进行比较这个是对设置的时间Notification的setWhen设置的值,层层比较,前边属性最重要,如果相等比对下面的属性,最后比较时间。
好了就这些