不積跬步無以至千里
最近被提了一個關於通知欄上通知排序的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設置的值,層層比較,前邊屬性最重要,如果相等比對下面的屬性,最後比較時間。
好了就這些