关于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设置的值,层层比较,前边属性最重要,如果相等比对下面的属性,最后比较时间。

好了就这些

猜你喜欢

转载自blog.csdn.net/wdyshowtime/article/details/80592469