SystemUI的通知栏通知的排序

不积跬步无以至千里

       最近被提了一个关于通知栏上通知排序的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方法

 


 
 
  1. void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
  2. final int callingPid, final String tag, final int id, final Notification notification,
  3. int[] idOut, int incomingUserId) {
  4. if (DBG) {
  5. Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
  6. + " notification=" + notification);
  7. }
  8. /// M:仅仅是过滤一些特殊的通知流,正常的可以忽略{
  9. boolean foundTarget = false;
  10. if (pkg != null && pkg.contains(".stub") && notification != null) {
  11. String contentTitle = notification.extras != null ?
  12. notification.extras.getString(Notification.EXTRA_TITLE) : " ";
  13. if (contentTitle != null && contentTitle.startsWith("notify#")) {
  14. foundTarget = true;
  15. Slog.d(TAG, "enqueueNotification, found notification, callingUid: " + callingUid
  16. + ", callingPid: " + callingPid + ", pkg: " + pkg
  17. + ", id: " + id + ", tag: " + tag);
  18. }
  19. }
  20. /// @}
  21. //检测是否为Phone进程或者系统进程,是否为同一个uid发送,
  22.  checkCallerIsSystemOrSameApp(pkg);
  23. //是否为系统通知
  24. final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
  25. //是否为NotificationListenerService监听
  26.  final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);
  27. final int userId = ActivityManager.handleIncomingUser(callingPid,
  28. callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
  29. final UserHandle user = new UserHandle(userId);
  30. // Fix the notification as best we can.
  31. try {
  32. final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser(
  33. pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
  34. (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
  35. Notification.addFieldsFromContext(ai, userId, notification);
  36. } catch (NameNotFoundException e) {
  37. Slog.e(TAG, "Cannot create a context for sending app", e);
  38. return;
  39. }
  40. mUsageStats.registerEnqueuedByApp(pkg);
  41. if (pkg == null || notification == null) {
  42. throw new IllegalArgumentException("null not allowed: pkg=" + pkg
  43. + " id=" + id + " notification=" + notification);
  44. }
  45. //创建一个StatusBarNotification对象
  46. final StatusBarNotification n = new StatusBarNotification(
  47. pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,
  48. user);
  49.      //这里系统限制了每个应用的发送通知的数量,来阻止DOS攻击防止泄露,这块Toast也有这块的处理会对一个应用发送的条数做处理
  50. if (!isSystemNotification && !isNotificationFromListener) {
  51. synchronized (mNotificationList) {
  52.     //判断是否为一个新的通知还是更新
  53. if(mNotificationsByKey.get(n.getKey()) != null) {
  54. // this is an update, rate limit updates only
  55. final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
  56. if (appEnqueueRate > mMaxPackageEnqueueRate) {
  57. mUsageStats.registerOverRateQuota(pkg);
  58. final long now = SystemClock.elapsedRealtime();
  59. if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
  60. Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
  61. + ". Shedding events. package=" + pkg);
  62. mLastOverRateLogTime = now;
  63. }
  64. return;
  65. }
  66. }
  67. int count = 0;
  68. final int N = mNotificationList.size();
  69. for (int i=0; i <N; i++) {
  70. final NotificationRecord r = mNotificationList.get(i);
  71. if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) {
  72. if (r.sbn.getId() == id && TextUtils.equals(r.sbn.getTag(), tag)) {
  73. break; //如果是已存在的通知,是更新操作直接跳出
  74. }
  75. count++;
  76. //当一个应用的通知数量大于限制数量值时报错,单应用最大数量为50
  77. if (count >= MAX_PACKAGE_NOTIFICATIONS) {
  78. mUsageStats.registerOverCountQuota(pkg);
  79. Slog.e(TAG, "Package has already posted " + count
  80. + " notifications. Not showing more. package=" + pkg);
  81. return;
  82. }
  83. }
  84. }
  85. }
  86. }
  87. // 白名单的延迟意图
  88. if (notification.allPendingIntents != null) {
  89. final int intentCount = notification.allPendingIntents.size();
  90. if (intentCount > 0) {
  91. final ActivityManagerInternal am = LocalServices
  92. .getService(ActivityManagerInternal.class);
  93. final long duration = LocalServices.getService(
  94. DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();
  95. for (int i = 0; i < intentCount; i++) {
  96. PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
  97. if (pendingIntent != null) {
  98. am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration);
  99. }
  100. }
  101. }
  102. }
  103. // 过滤容错处理是否为优先级输入有问题
  104. notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
  105. Notification.PRIORITY_MAX);
  106. // 创建NotificationRecord
  107. final NotificationRecord r = new NotificationRecord(getContext(), n);
  108. //创建Runnable,后边操作在这个Runnable
  109.  mHandler.post(new EnqueueNotificationRunnable(userId, r));
  110. idOut[0] = id;
  111. /// 过滤前边处理的特殊的通知,普通的通知不用考虑 @{
  112. if (foundTarget) {
  113. try {
  114. Thread.sleep(1000);
  115. } catch (InterruptedException exception) {
  116. // ignore it.
  117. }
  118. }
  119. /// @}
  120. }

总结上边代码,主要有个过滤的特殊通知处理,创建了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中,接着看一下这个内部类

 


 
 
  1. private class EnqueueNotificationRunnable implements Runnable {
  2. private final NotificationRecord r;
  3. private final int userId;
  4. EnqueueNotificationRunnable(int userId, NotificationRecord r) {
  5. this.userId = userId;
  6. this.r = r;
  7. };
  8. @Override
  9. public void run() {
  10. synchronized (mNotificationList) {
  11. final StatusBarNotification n = r.sbn;
  12. if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
  13. //根据key值获取Old NotificationRecord
  14.  NotificationRecord old = mNotificationsByKey.get(n.getKey());
  15. if (old != null) {
  16. // 获取ranking 信息从old NotificationRecord
  17. r.copyRankingInformation(old);
  18. }
  19. final int callingUid = n.getUid();
  20. final int callingPid = n.getInitialPid();
  21. final Notification notification = n.getNotification();
  22. final String pkg = n.getPackageName();
  23. final int id = n.getId();
  24. final String tag = n.getTag();
  25. final boolean isSystemNotification = isUidSystem(callingUid) ||
  26. ("android".equals(pkg));
  27. // Handle grouped notifications and bail out early if we
  28. // can to avoid extracting signals.
  29. handleGroupedNotificationLocked(r, old, callingUid, callingPid);
  30. // This conditional is a dirty hack to limit the logging done on
  31. // behalf of the download manager without affecting other apps.
  32. if (!pkg.equals("com.android.providers.downloads")
  33. || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
  34. int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
  35. if (old != null) {
  36. enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
  37. }
  38. EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
  39. pkg, id, tag, userId, notification.toString(),
  40. enqueueStatus);
  41. }
  42. mRankingHelper.extractSignals(r);
  43. final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
  44. // blocked apps
  45. if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
  46. || !noteNotificationOp(pkg, callingUid) || isPackageSuspended) {
  47. if (!isSystemNotification) {
  48. if (isPackageSuspended) {
  49. Slog.e(TAG, "Suppressing notification from package due to package "
  50. + "suspended by administrator.");
  51. mUsageStats.registerSuspendedByAdmin(r);
  52. } else {
  53. Slog.e(TAG, "Suppressing notification from package by user request.");
  54. mUsageStats.registerBlocked(r);
  55. }
  56. return;
  57. }
  58. }
  59. // tell the ranker service about the notification
  60. if (mRankerServices.isEnabled()) {
  61. mRankerServices.onNotificationEnqueued(r);
  62. // TODO delay the code below here for 100ms or until there is an answer
  63. }
  64. int index = indexOfNotificationLocked(n.getKey());
  65. if (index < 0) {
  66. mNotificationList.add(r);
  67. mUsageStats.registerPostedByApp(r);
  68. } else {
  69. old = mNotificationList.get(index);
  70. mNotificationList.set(index, r);
  71. mUsageStats.registerUpdatedByApp(r, old);
  72. // Make sure we don't lose the foreground service state.
  73. notification.flags |=
  74. old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
  75. r.isUpdate = true;
  76. }
  77.            
  78. mNotificationsByKey.put(n.getKey(), r);
  79. // Ensure if this is a foreground service that the proper additional
  80. // flags are set.
  81. if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
  82. notification.flags |= Notification.FLAG_ONGOING_EVENT
  83. | Notification.FLAG_NO_CLEAR;
  84. }
  85. applyZenModeLocked(r);
  86. //******排序操作*********//
  87.  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设置的值,层层比较,前边属性最重要,如果相等比对下面的属性,最后比较时间。

好了就这些

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值