關於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方法

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設置的值,層層比較,前邊屬性最重要,如果相等比對下面的屬性,最後比較時間。

好了就這些

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章